@fastpix/supabase provides a CLI that integrates your FastPix account with your Supabase project. The integration mirrors your FastPix media, live streams, and uploads into Postgres, and keeps them current from webhooks.
Setting up the integration does the following:
- Creates a
fastpixschema that contains the following tables:media, for Medialive_streams, for Live Streamsuploads, for Direct Uploadswebhook_events, for the raw webhook event logsync_state, for backfill and reconcile bookkeeping
- Creates Edge Functions that receive webhooks and keep the data in the
fastpixschema up to date.
- Before you begin
- Set up the integration locally
- Understand how syncing works
- Backfill and reconcile data
- Trigger a function when an event syncs
- Deploy to production
- Secure the tables
- Upgrade the engine
- CLI reference
- Environment variable reference
- Troubleshoot
Make sure you have the following:
- Node.js version 20 or later.
- Docker, which local Supabase requires.
- A Supabase project initialized in your repository. If your project doesn't contain a
supabasedirectory, runnpx supabase init. For more information, see Supabase CLI init. - A running local Supabase instance. To start one, run
npx supabase start. - Your Supabase service-role key. To find it, run
npx supabase status -o env. - A FastPix account with an API token ID and token secret. The
initcommand prompts you for both. - Access to the FastPix dashboard, so that you can create a webhook and copy its signing secret.
With your local Supabase instance running, run the init command and follow the prompts:
npx @fastpix/supabase initThe command does the following:
- Creates the
fastpixschema and its tables. - Creates four functions in
supabase/functions:fastpix-webhook,fastpix-worker,fastpix-reconcile, andfastpix-backfill. These functions use the@fastpix/fp-sync-enginepackage 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 to configure
FASTPIX_TOKEN_ID,FASTPIX_TOKEN_SECRET, andFASTPIX_WEBHOOK_SECRET. The command writes these tosupabase/functions/.env. - Sets
verify_jwtfor each function inconfig.toml. The webhook is set tofalsebecause FastPix signs its requests instead of sending a Supabase JWT.
Everything init writes is copy-if-absent, so re-running the command is safe and never overwrites a file that you edited.
NOTE:
If Supabase was already running when you raninit, restart the instance withnpx supabase stopandnpx supabase startso that theconfig.tomlchanges take effect.
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 nothing reports an error.
In the SQL Editor, run the following:
select vault.create_secret('http://host.docker.internal:54321/functions/v1', 'fastpix_functions_url');
select 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.
-
Serve the Edge Functions on port 54321:
npx supabase functions serve
-
Expose the functions to the internet with a tunneling service such as ngrok. Your public webhook URL has a format like
https://d99d3b847eb8.ngrok-free.app/functions/v1/fastpix-webhook. -
In the FastPix dashboard, go to Org Settings > Webhooks > Create new webhook and enter that URL.
-
Copy the webhook secret from FastPix into
FASTPIX_WEBHOOK_SECRETinsupabase/functions/.env.
-
In the FastPix dashboard, confirm that you're in the correct environment, then upload a media asset.
-
In your local Supabase dashboard, open the
mediatable in thefastpixschema. A row appears for the media, and itsmediaIdmatches the ID shown in the FastPix dashboard.
If the row doesn't appear, the event log shows where processing stopped:
select "type", "processStatus", "lastError" from fastpix.webhook_events
order 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, notselect mediaId.
- The
fastpixschema isn't exposed in the[api]section ofconfig.toml. The service role writes these tables, and your client can't read them until you expose a safe view. For more information, see Secure the tables.
Most FastPix integrations need to save data into a database. You can add videos to FastPix in the following ways:
- Upload them directly with the Direct Uploads API.
- Provide a URL that points to a publicly available video file.
- Upload them in the FastPix dashboard.
When you upload a video or audio file to FastPix, it becomes a media asset.
An application typically saves FastPix information such as the media ID, playback ID, duration, and aspect ratio. @fastpix/supabase saves all of this for you under the fastpix schema.
An application also tracks information that FastPix doesn't own, such as the following:
- Which user uploaded the video.
- Structural details, such as whether the video belongs to a series or relates to other videos.
- Presentation details, such as descriptions, summaries, and chapter markers.
- Which users can view the video.
@fastpix/supabase doesn't save this kind of information, because it's an application-level concern.
NOTE:
FastPix media includes ametadatafield, which the integration saves to themetadataJSON column in themediatable. Higher-level concepts such as titles, chapters, and permissions remain application-level concerns.
The integration stores webhooks first and processes them second. fastpix-webhook verifies the signature and puts the event on the queue, and fastpix-worker drains that queue. As a result, a failed projection never loses a delivery.
Events that carry only a fragment of a record, such as playback IDs, tracks, or simulcast targets, are recorded and acknowledged but not projected. FastPix sends a parent video.media.updated event afterward that carries the whole record. The nightly reconcile is the backstop for anything that never arrives.
If your FastPix account already contains media or live streams, backfill them into your Supabase database:
npx @fastpix/supabase backfillThe 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_streamsBackfill 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, re-fetch resources that were active in the last N hours:
npx @fastpix/supabase reconcile # last 24 hours
npx @fastpix/supabase reconcile 48 # last 48 hoursIf you'd rather not use the CLI, call the sync engine from your own code:
import { FastPixSync } from "@fastpix/fp-sync-engine";
const fastpixSync = new FastPixSync({
databaseUrl: "your-supabase-database-url",
fastpixTokenId: "your-fastpix-token-id",
fastpixTokenSecret: "your-fastpix-token-secret",
fastpixWebhookSecret: "your-fastpix-webhook-secret",
});
// Backfill all data.
await fastpixSync.syncBackfill({ object: "all" });
// Backfill specific object types.
await fastpixSync.syncBackfill({ object: "media" });
await fastpixSync.syncBackfill({ object: "live_streams" });
// Reconcile resources active in the last 24 hours.
const result = await fastpixSync.reconcileRecent({ sinceHours: 24 });
console.log(
`Reconciled ${result.media} media, ${result.liveStreams} live streams`,
);You can trigger your own Edge Function when a specific event finishes syncing. For example, you can run content moderation when a media asset becomes ready.
Workflows run on the pgmq queue and the Supabase Cron jobs that init already created, so complete the local setup first. You only need to map an event type to one or more function names.
-
Define the workflow with the
FASTPIX_WORKFLOWSsecret:npx supabase secrets set FASTPIX_WORKFLOWS='{"video.media.ready":["content-moderation"]}'
With this mapping, when the
video.media.readyevent fires and thefastpix.mediarow is already current, the integration runs your Edge Function namedcontent-moderation. -
Create the function:
npx supabase functions new content-moderation
-
Open
supabase/functions/content-moderation/index.tsand add your handler. Your function receives{ eventId, type, objectType, objectId }.Deno.serve(async (req) => { try { const { objectId } = await req.json(); if (!objectId) { console.log("No objectId in payload"); return new Response("No objectId in payload", { status: 500 }); } console.log(`Running moderation for media: ${objectId}`); // The fastpix.media row for objectId is already up to date. // Call APIs, write to your database, or run other logic here. return new Response("Moderation complete", { status: 200 }); } catch (error) { console.error("Error running content-moderation.ts:", error); return new Response(JSON.stringify({ error: "500" }), { status: 500, headers: { "Content-Type": "application/json" }, }); } });
-
Register the function in
config.tomlwithverify_jwt = true, because the worker sends the service-role key:[functions.content-moderation] verify_jwt = true
Caution: Dispatch is at-least-once, so your function might run more than once for the same event. Make it safe to repeat.
Run npx supabase functions serve to execute all of your functions, then create a media asset either programmatically or by uploading a video in the FastPix dashboard. When the media becomes ready for playback, the video.media.ready event fires and your content-moderation function runs.
-
Link your project:
npx supabase login npx supabase link --project-ref <ref>
-
Run the migrations.
db pushapplies the queue and cron migrations, andmigratecreates thefastpixtables. Find your connection string in the dashboard under Connect.npx supabase db push SUPABASE_DB_URL='postgresql://…' npx @fastpix/supabase migrate -
Add the Vault secrets that the cron jobs need. In the dashboard, open the SQL Editor and run the following:
select vault.create_secret('https://<ref>.supabase.co/functions/v1', 'fastpix_functions_url'); select vault.create_secret('<service-role-key>', 'fastpix_service_role_key');
Re-running these statements after the secrets exist fails with a duplicate key error. To change a value, update it instead:
select vault.update_secret( (select id from vault.secrets where name = 'fastpix_service_role_key'), '<new-key>');
-
Set the secrets for your Edge Functions, either manually in the dashboard or from your
.envfile. Confirm thatsupabase/functions/.envholds the correct credentials first.npx supabase secrets set --env-file ./supabase/functions/.env npx supabase secrets list -
Push the config. This step sends the
verify_jwtsettings. Without it, the webhook returns401.npx supabase config push
-
Deploy the functions:
npx supabase functions deploy
-
Set up the production webhook. After you deploy
fastpix-webhook, its URL appears in the dashboard under Edge Functions. As in Run the webhook locally, create a webhook in FastPix with that URL, then setFASTPIX_WEBHOOK_SECRETin the Supabase dashboard to the new value. -
Secure the tables. They're unsecured until you do.
Warning: Until you complete this section, 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:
grant usage on schema fastpix to service_role;
grant all on all tables in schema fastpix to service_role;
alter default privileges in schema fastpix grant all on tables to service_role;
alter table fastpix.webhook_events enable row level security;
alter table fastpix.media enable row level security;
alter table fastpix.live_streams enable row level security;
alter table fastpix.uploads enable row level security;
alter 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.
Each generated function is a three-line entry point that imports its handler from @fastpix/supabase/deno, pinned to an exact version in that function's deno.json. That pin, not your package.json, determines which engine your deployed webhooks run.
Because init never overwrites existing files, upgrading the package doesn't update functions that you already have. If the pin and your installed package disagree, the CLI commands run the new engine while your webhooks still run the old one.
To upgrade, do one of the following:
- Bump the version pin in each function's
deno.jsonby hand. - Delete the four function directories and re-run
npx @fastpix/supabase init.
After you upgrade @fastpix/fp-sync-engine, run migrate, because an engine upgrade can change column types:
npx @fastpix/supabase migrate| Command | Description |
|---|---|
npx @fastpix/supabase init |
Creates the schema, the four Edge Functions, the pgmq queue, and the cron jobs, then prompts for credentials. Copy-if-absent, so it's safe to re-run. |
npx @fastpix/supabase backfill [media|live_streams] |
Imports existing FastPix records. Omit the argument to import everything. Resumable. |
npx @fastpix/supabase reconcile [hours] |
Re-fetches resources active in the last N hours. Defaults to 24. |
npx @fastpix/supabase migrate |
Creates or updates the fastpix tables without copying any files. Run after an engine upgrade. |
Set the following variables in supabase/functions/.env for local development, and with npx supabase secrets set in production.
| Variable | Required by | Description |
|---|---|---|
FASTPIX_WEBHOOK_SECRET |
All four functions | The FastPix webhook signing secret, used to verify request signatures. Copy it from the FastPix dashboard. |
FASTPIX_TOKEN_ID |
Worker, reconcile, backfill | The FastPix API token ID, used to re-fetch records. |
FASTPIX_TOKEN_SECRET |
Worker, reconcile, backfill | The FastPix API token secret. |
FASTPIX_WORKFLOWS |
Worker | Optional. A JSON map of event types to Edge Function names. For more information, see Trigger a function when an event syncs. |
SUPABASE_DB_URL |
The migrate command |
The Postgres connection string. Supabase injects this into Edge Functions. Find it in the dashboard under Connect. |
The following secrets live in Supabase Vault rather than in your environment:
| Secret | Description |
|---|---|
fastpix_functions_url |
The base URL that the cron jobs call. Use http://host.docker.internal:54321/functions/v1 locally. |
fastpix_service_role_key |
The service-role key that the cron jobs send as a bearer token. |
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.
The cron jobs report bearer token errors. The fastpix_service_role_key Vault secret is missing or wrong. In the dashboard, go to Integrations > Vault and check its value. Vault values load when migrations are applied.
To test while ignoring JWT verification, serve the functions with verification disabled:
npx supabase functions serve --no-verify-jwtconfig.toml changes have no effect. Restart your local instance with npx supabase stop and npx supabase start.
vault.create_secret fails with a duplicate key error. The secret already exists. Update it instead of creating it. See step 3 of Deploy to production.
A SQL query returns a column-not-found error. Column names are camelCase and need double quotes: select "mediaId" from fastpix.media.