Integrate FastPix with Supabase

The FastPix Supabase integration syncs your FastPix media, live streams, and uploads to your Supabase database using webhooks and edge functions.

The @fastpix/supabase package provides a CLI that creates a fastpix schema in your database, deploys the edge functions that process webhooks, and keeps your data current automatically.


What you’ll get

After setup, your Supabase database has a fastpix schema that contains the following tables:

  • media stores your on-demand video assets and their metadata.
  • live_streams stores live stream configurations, status, and secrets.
  • uploads stores direct-upload sessions.
  • webhook_events stores the raw webhook event log for debugging and auditing.
  • sync_state stores backfill and reconcile bookkeeping.

The CLI also creates four edge functions (fastpix-webhook, fastpix-worker, fastpix-reconcile, and fastpix-backfill), a pgmq job queue, and two Supabase Cron jobs, a queue drain and a nightly reconcile, that drive the sync.


Prerequisites

Before you begin, make sure you have the following:

  • A FastPix account with an API token ID and token secret. To generate them, see Activate your account.
  • A Supabase project with the Supabase CLI installed.
  • Node.js version 20 or later.
  • Docker, which local Supabase requires.
  • Your Supabase service-role key. To find it, run npx supabase status -o env.
  • Access to the FastPix dashboard, so that you can create a webhook and copy its signing secret.

Initialize the integration

Make sure Supabase is initialized in your project, and then initialize the FastPix integration:

$npx supabase init
$npx @fastpix/supabase init

The init command does the following:

  • Creates the fastpix schema and its tables.
  • Creates four functions in supabase/functions: fastpix-webhook, fastpix-worker, fastpix-reconcile, and fastpix-backfill. These functions use the @fastpix/fp-sync-engine package to sync your data.
  • Sets up a pgmq job queue and two Supabase Cron jobs, a queue drain and a nightly reconcile, that drive the sync.
  • Prompts you for FASTPIX_TOKEN_ID, FASTPIX_TOKEN_SECRET, and FASTPIX_WEBHOOK_SECRET, and writes them to supabase/functions/.env.
  • Sets verify_jwt for each function in config.toml. The webhook is set to false, because FastPix signs its requests instead of sending a Supabase JWT.

Everything init writes is copy-if-absent, so you can re-run the command safely without overwriting a file that you edited.

NOTE: If Supabase was already running when you ran init, restart it with npx supabase stop and npx supabase start so that the config.toml changes take effect.


Configure environment variables

The init command prompts you for your credentials and writes them to supabase/functions/.env. If you skipped a value or need to change one, update the file directly:

$FASTPIX_TOKEN_ID=your-fastpix-token-id
$FASTPIX_TOKEN_SECRET=your-fastpix-token-secret
$FASTPIX_WEBHOOK_SECRET=your-webhook-secret

Find your token ID and secret in the FastPix dashboard. You create the webhook secret in step 4, when you configure the webhook.


Create the Vault secrets

The two cron jobs call your edge functions over HTTP. They read the function URL and the service-role key from Supabase Vault, so that neither value is committed in a migration. Until these secrets exist, the queue fills but nothing drains, and no error is reported.

In the Supabase SQL Editor, run the following:

1select vault.create_secret('http://host.docker.internal:54321/functions/v1', 'fastpix_functions_url');
2select vault.create_secret('<SERVICE_ROLE_KEY>', 'fastpix_service_role_key');

Locally, the URL must be reachable from inside the Postgres container, so use host.docker.internal rather than localhost. To find your service-role key, run npx supabase status -o env.


Run locally and configure the webhook

  1. Serve the edge functions on port 54321:

    $npx supabase functions serve
  2. Expose the functions to the internet with a tunneling tool such as ngrok. Your public webhook URL has a format like https://d99d3b847eb8.ngrok-free.app/functions/v1/fastpix-webhook.

  3. In the FastPix dashboard, go to Org Settings > Webhooks > Create new webhook and enter that URL. For more information, see Set up webhooks.

  4. Copy the signing secret from FastPix into FASTPIX_WEBHOOK_SECRET in supabase/functions/.env.


Verify that it works

  1. In the FastPix dashboard, confirm that you’re in the correct environment, and then upload a media asset.

  2. In your local Supabase dashboard, open the media table in the fastpix schema. A row appears for the media, and its mediaId matches the ID shown in the FastPix dashboard.

If the row doesn’t appear, query the event log to see where processing stopped:

1select "type", "processStatus", "lastError" from fastpix.webhook_events
2order by "receivedAt" desc limit 10;

For how to interpret the result, see Troubleshoot.

NOTE: Column names match the FastPix API, so they’re camelCase and require double quotes in SQL. Use select "mediaId" from fastpix.media, not select mediaId.


Backfill existing data

If your FastPix account already contains media or live streams, backfill them into your Supabase database:

$npx @fastpix/supabase backfill

The command prompts you for the database URL where it stores the data, and for your FastPix token ID and secret. To sync a single object type, pass it as an argument:

$npx @fastpix/supabase backfill media
$npx @fastpix/supabase backfill live_streams

Backfill saves its place as it goes, so if it stops, you can run it again and it continues from where it left off.

To heal webhooks that you missed, reconcile resources that were active in the last few hours:

$npx @fastpix/supabase reconcile # last 24 hours
$npx @fastpix/supabase reconcile 48 # last 48 hours

Backfill programmatically

You can also run the backfill from your own code with the sync engine:

1import { FastPixSync } from "@fastpix/fp-sync-engine";
2
3const fastpixSync = new FastPixSync({
4 databaseUrl: "your-supabase-database-url",
5 fastpixTokenId: "your-fastpix-token-id",
6 fastpixTokenSecret: "your-fastpix-token-secret",
7 fastpixWebhookSecret: "your-fastpix-webhook-secret",
8});
9
10const result = await fastpixSync.syncBackfill({ object: "all" });

Deploy to production

  1. Link your project:

    $npx supabase login
    $npx supabase link --project-ref <ref>
  2. Run the migrations. db push applies the queue and cron migrations, and migrate creates the fastpix tables. Find your connection string in the dashboard under Connect.

    $npx supabase db push
    $SUPABASE_DB_URL='postgresql://...' npx @fastpix/supabase migrate
  3. Add the Vault secrets that the cron jobs need. In the SQL Editor, run the following:

    1select vault.create_secret('https://<ref>.supabase.co/functions/v1', 'fastpix_functions_url');
    2select vault.create_secret('<service-role-key>', 'fastpix_service_role_key');

    If a secret already exists, update it instead of creating it again:

    1select vault.update_secret(
    2 (select id from vault.secrets where name = 'fastpix_service_role_key'), '<new-key>');
  4. Set the secrets for your edge functions from your .env file. Confirm that supabase/functions/.env holds the correct credentials first.

    $npx supabase secrets set --env-file ./supabase/functions/.env
    $npx supabase secrets list
  5. Push the config. This step sends the verify_jwt settings. Without it, the webhook returns 401.

    $npx supabase config push
  6. Deploy the functions:

    $npx supabase functions deploy
  7. Set up the production webhook. After you deploy fastpix-webhook, its URL appears in the dashboard under Edge Functions. Create a webhook in FastPix with that URL, and then set FASTPIX_WEBHOOK_SECRET in the Supabase dashboard to the new signing secret.

After you deploy, secure the tables as described in the next section.


Secure the tables

WARNING: Until you complete this step, the fastpix tables have no row-level security. live_streams holds streamKey and srtSecret, which let anyone stream into your account, and webhook_events stores raw payloads that can contain those same secrets. Never expose either table to your client.

In the SQL Editor, grant access to the service role and enable row-level security on every table:

1grant usage on schema fastpix to service_role;
2grant all on all tables in schema fastpix to service_role;
3alter default privileges in schema fastpix grant all on tables to service_role;
4
5alter table fastpix.webhook_events enable row level security;
6alter table fastpix.media enable row level security;
7alter table fastpix.live_streams enable row level security;
8alter table fastpix.uploads enable row level security;
9alter table fastpix.sync_state enable row level security;

Enabling row-level security with no policies denies everyone except the service role, which is the safe default. To show data in your app, create a view that exposes only the safe columns.


Query your data

The fastpix schema is written by the service role and isn’t exposed to your client until you add a safe view. For server-side queries, use the service-role key, which bypasses row-level security:

1import { createClient } from "@supabase/supabase-js";
2
3const supabase = createClient(
4 "your-supabase-url",
5 "your-supabase-service-role-key",
6 { db: { schema: "fastpix" } },
7);
8
9// Get all ready media assets.
10const { data: media } = await supabase
11 .from("media")
12 .select("*")
13 .eq("status", "ready");

WARNING: The service-role key bypasses row-level security and belongs only in server-side code. Never expose it in a client application. To read FastPix data from the client, add your own row-level security policies or expose a view with only the safe columns.

Column names match the FastPix API, so they’re camelCase and require double quotes in SQL. Use select "mediaId" from fastpix.media, not select mediaId.


Use the sync engine with any Postgres

If you don’t use Supabase, use the @fastpix/fp-sync-engine package directly. It’s a framework-free TypeScript library that works with any Postgres database and runs on both Node.js and Deno.

Install the package:

$npm install @fastpix/fp-sync-engine

Create the schema once, initialize the engine, and process webhooks in your own server. The following example uses Express:

1import express from "express";
2import { FastPixSync, runMigrations, InvalidSignatureError } from "@fastpix/fp-sync-engine";
3
4// Create or upgrade the `fastpix` schema. Safe to run on every boot.
5await runMigrations({ databaseUrl: process.env.DATABASE_URL });
6
7const sync = new FastPixSync({
8 databaseUrl: process.env.DATABASE_URL,
9 fastpixWebhookSecret: process.env.FASTPIX_WEBHOOK_SECRET,
10 fastpixTokenId: process.env.FASTPIX_TOKEN_ID,
11 fastpixTokenSecret: process.env.FASTPIX_TOKEN_SECRET,
12});
13
14const app = express();
15
16// Take the raw body. The engine verifies the signature over the exact bytes.
17app.post("/webhook", express.text({ type: "*/*" }), async (req, res) => {
18 try {
19 await sync.processWebhook(req.body, req.headers);
20 res.sendStatus(202);
21 } catch (err) {
22 res.sendStatus(err instanceof InvalidSignatureError ? 401 : 500);
23 }
24});
25
26app.listen(3000);

The engine provides the same backfill and reconcile capabilities as the CLI. For the full configuration and API reference, see the @fastpix/fp-sync-engine README.


Troubleshoot

No rows in fastpix.webhook_events.

FastPix isn’t reaching your webhook. Check that your tunnel URL is current and that the webhook secret in FastPix matches FASTPIX_WEBHOOK_SECRET.

Rows stuck at received.

Nothing is draining the queue. Confirm that both Vault secrets exist and hold the correct values. See Create the Vault secrets.

The webhook returns 401 in production.

You didn’t push the config, so verify_jwt is still true for fastpix-webhook. Run npx supabase config push.

A SQL query returns a column-not-found error.

Column names are camelCase and need double quotes: select "mediaId" from fastpix.media.


Resources