> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://fastpix.com/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://fastpix.com/docs/_mcp/server.

# Integrate FastPix with Supabase

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 build

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.

```text
                    FastPix
         (Media, Live Streams, Uploads)
                      │
                      │
              Webhook Events
                      │
                      ▼
        ┌──────────────────────────┐
        │   fastpix-webhook        │
        │ (Supabase Edge Function) │
        └──────────────────────────┘
                      │
             Stores Event in Queue
                      │
                      ▼
        ┌──────────────────────────┐
        │        PGMQ Queue        │
        └──────────────────────────┘
                      │
          Cron Job (every 10 sec)
                      │
                      ▼
        ┌──────────────────────────┐
        │    fastpix-worker        │
        │ Processes Queue Events   │
        └──────────────────────────┘
                      │
          Fetch Latest Resource
                      │
                      ▼
                 FastPix API
                      │
                      ▼
        ┌──────────────────────────┐
        │     fastpix schema       │
        │  media                   │
        │  live_streams            │
        │  uploads                 │
        │  webhook_events          │
        │  sync_state              │
        └──────────────────────────┘
```

\


## Prerequisites

Before you begin, make sure you have the following:

* A [FastPix account](https://dashboard.fastpix.com/signup) with an API token ID and token secret. To generate them, see [Activate your account](https://fastpix.com/docs/getting-started/activate-your-account).
* Access to the FastPix dashboard, so that you can create a webhook and copy its signing secret.
* A [Supabase project](https://supabase.com/dashboard).
* The [Supabase CLI](https://supabase.com/docs/guides/cli). Every command on this page runs it through `npx`, so a global install is optional.
* Node.js version 20 or later.
* Docker. See [Start Docker](#start-docker).
* A tunneling tool such as [ngrok](https://ngrok.com/), to expose your local webhook endpoint to FastPix.

\


## Setup overview

Work through these in order. Each one depends on the one before it.

| What you do                                                                      | Where                            |
| -------------------------------------------------------------------------------- | -------------------------------- |
| [Start Docker](#start-docker)                                                    | Docker Desktop                   |
| [Start Supabase](#start-supabase)                                                | Terminal                         |
| [Initialize FastPix, then restart Supabase](#initialize-the-fastpix-integration) | Terminal                         |
| [Add the Vault secrets](#add-the-vault-secrets)                                  | Supabase SQL Editor              |
| [Expose your local server and serve the functions](#expose-your-local-server)    | Two terminals                    |
| [Create the webhook and add its secret](#create-the-webhook)                     | FastPix dashboard, then terminal |
| [Upload a video and confirm it synced](#verify-that-it-works)                    | Both dashboards                  |

\


## Start Docker

Local Supabase runs as Docker containers, so Docker must be running before anything else.

1. Install [Docker Desktop](https://docs.docker.com/get-started/get-docker/) (macOS and Windows) or [Docker Engine](https://docs.docker.com/engine/install/) (Linux).
2. Start it. On macOS and Windows, open the app and wait for the whale icon to stop animating.
3. Confirm it's running:

   ```bash
   docker info
   ```

If `docker info` prints server details, you're ready. If it reports `Cannot connect to the Docker daemon`, Docker is installed but not started.

> **NOTE:** Local Supabase starts about a dozen containers. Give Docker at least 4 GB of RAM.

\


## Start Supabase

Initialize Supabase in your project and start it:

```bash
npx supabase init
npx supabase start
```

`start` prints your local credentials. To see them again at any time:

```bash
npx supabase status -o env
```

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

\


## Initialize the FastPix integration

With Supabase running, initialize the integration:

```bash
npx @fastpix/supabase init
```

This copies three migrations into `supabase/migrations` for the job queue and cron jobs, creates the four edge functions in `supabase/functions`, and sets `verify_jwt` in `config.toml` — `false` for the webhook, because FastPix signs its requests instead of sending a Supabase JWT.

It then prompts you for four values:

| Prompt                   | What to enter                                                                                                   |
| ------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `FASTPIX_TOKEN_ID`       | Paste from the FastPix dashboard.                                                                               |
| `FASTPIX_TOKEN_SECRET`   | Paste from the FastPix dashboard.                                                                               |
| `FASTPIX_WEBHOOK_SECRET` | **Press enter to skip.** This secret doesn't exist yet — FastPix creates it later, when you register a webhook. |
| Database URL             | Paste the `DB_URL` value from the previous section. This is where the `fastpix` tables get created.             |

> **NOTE:** If you skip the database URL, no tables are created. The rest of the setup will look fine, but nothing will ever sync. Create them later with:
>
> ```bash
> SUPABASE_DB_URL='postgresql://...' npx @fastpix/supabase migrate
> ```

Now restart Supabase so it picks up the `config.toml` changes and loads the new functions:

```bash
npx supabase stop && npx supabase start
```

> **NOTE:** Don't skip the restart. Without it, the webhook endpoint returns `401` and FastPix won't accept your URL.

\


## Add the Vault secrets

The cron jobs call your edge functions over HTTP, and they read the function URL and service-role key from Supabase Vault so that neither value is committed in a migration.

Do this now, before you set up the webhook. Without these secrets, events arrive and sit in the queue forever — nothing drains, and no error is reported.

Open the SQL Editor at `http://127.0.0.1:54323` and run:

```sql
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');
```

Replace `<SERVICE_ROLE_KEY>` with the value from `npx supabase status -o env`.

Use `host.docker.internal` rather than `localhost` — the URL has to be reachable from inside the Postgres container, where `localhost` means the container itself.

\


## Expose your local server

FastPix delivers webhooks over the public internet, so your local Supabase needs a public address.

1. In one terminal, expose port 54321:

   ```bash
   ngrok http 54321
   ```

   Leave it running. Copy the forwarding address it prints, then add `/functions/v1/fastpix-webhook` to it. That's your webhook URL:

   ```
   https://d99d3b847eb8.ngrok-free.app/functions/v1/fastpix-webhook
   ```

2. In a second terminal, serve the edge functions:

   ```bash
   npx supabase functions serve
   ```

   Leave this running too.

3. Confirm the endpoint answers, using your own URL:

   ```bash
   curl https://d99d3b847eb8.ngrok-free.app/functions/v1/fastpix-webhook
   ```

   You should get back `ok`. Don't move on until you do — FastPix checks this URL next, and won't save it otherwise.

   * A `404` means `functions serve` isn't running, or your ngrok URL has changed.
   * A `401` means you skipped the restart after initializing FastPix.

\


## Create the webhook

1. In the FastPix dashboard, go to **Org Settings** > **Webhooks** > **Create new webhook**, and enter the URL from the previous section. For more information, see [Set up webhooks](/docs/webhooks/set-up-webhooks).

2. Copy the signing secret that FastPix shows you.

3. Open `supabase/functions/.env` and paste it in:

   ```bash
   FASTPIX_WEBHOOK_SECRET=your-webhook-secret
   ```

   > **WARNING:** Copy the secret exactly as the dashboard shows it. It's base64, and the engine decodes it before verifying signatures — a re-typed or truncated value makes every check fail silently, with no rows and no error.

4. Restart the functions. Go to the terminal running `functions serve`, press `Ctrl+C`, and start it again:

   ```bash
   npx supabase functions serve
   ```

   This isn't optional. `functions serve` reads `.env` once at startup, so the secret you just pasted has no effect until you restart. You only need to restart this one process, not the whole Supabase stack.

> **NOTE:** On ngrok's free tier the URL changes every time you restart the tunnel. If that happens, update the URL in the FastPix dashboard. The signing secret stays the same.

3. In the FastPix dashboard, go to **Org Settings** > **Webhooks** > **Create new webhook** and enter that URL. For more information, see [Set up webhooks](/docs/webhooks/set-up-webhooks).

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

\


## Verify that it works

Upload a media asset from the FastPix dashboard, then open the `media` table in the `fastpix` schema at `http://127.0.0.1:54323`. A row appears within a few seconds, and its `mediaId` matches the ID in FastPix.

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

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

* **No rows at all** — FastPix isn't reaching your webhook, or the signature is failing. Recheck your ngrok URL and the signing secret.
* **Rows stuck at `received`** — nothing is draining the queue. Recheck the Vault secrets.
* **Rows at `failed`** — read `lastError`. It's usually a wrong token ID or secret.

> **NOTE:** Column names match the FastPix API, so they're camelCase and need double quotes in SQL. Use `select "mediaId" from fastpix.media`, not `select mediaId`.

\


## Environment variables

`init` writes these to `supabase/functions/.env`:

| Variable                 | Required | Description                                                                                                                        |
| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `FASTPIX_TOKEN_ID`       | Yes      | API token ID from the FastPix dashboard.                                                                                           |
| `FASTPIX_TOKEN_SECRET`   | Yes      | API token secret.                                                                                                                  |
| `FASTPIX_WEBHOOK_SECRET` | Yes      | The webhook signing secret, base64. You get it when you register the webhook.                                                      |
| `FASTPIX_MAX_READ_CT`    | No       | How many queued events the worker claims per run. Defaults to `7`.                                                                 |
| `FASTPIX_WORKFLOWS`      | No       | Advanced. Lets the worker fan out to additional edge functions. See the [repository](https://github.com/FastPix/fastpix-supabase). |

You don't need to add `SUPABASE_DB_URL` — Supabase gives that to the edge functions on its own. It's separate from the database URL `init` asked for, which the CLI used to create the tables.

Whenever you change this file, restart `npx supabase functions serve`.

\


## Backfill existing data

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

```bash
npx @fastpix/supabase backfill
```

The command prompts for your database URL and FastPix credentials, or reads them from the environment. To sync a single object type, pass it as an argument:

```bash
npx @fastpix/supabase backfill media
npx @fastpix/supabase backfill live_streams
```

Backfill checkpoints as it goes, so if it stops you can run it again and it resumes.

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

```bash
npx @fastpix/supabase reconcile        # last 24 hours
npx @fastpix/supabase reconcile 48     # last 48 hours
```

\


### Backfill programmatically

```typescript
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: "", // not used by backfill or reconcile
});

await fastpixSync.syncBackfill({ object: "all" });

const result = await fastpixSync.reconcileRecent({ sinceHours: 24 });
console.log(result); // { media, liveStreams, deleted: { media, liveStreams } }

await fastpixSync.close();
```

\


## Deploy to production

The production sequence mirrors the local one: migrations, then Vault secrets, then functions, then the webhook.

1. Link your project:

   ```bash
   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**.

   ```bash
   npx supabase db push
   SUPABASE_DB_URL='postgresql://...' npx @fastpix/supabase migrate
   ```

   > **NOTE:** If your project already has migrations, `db push` fails with *"Found local migration files to be inserted before the last migration on remote database."* The FastPix migrations are numbered `0001`–`0003`, which sorts ahead of Supabase's timestamped filenames. Re-run with `npx supabase db push --include-all`.

3. Add the Vault secrets, the same as locally but with your production URL:

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

   If a secret already exists, update it instead:

   ```sql
   select vault.update_secret(
     (select id from vault.secrets where name = 'fastpix_service_role_key'), '<new-key>');
   ```

4. Set your API credentials as edge function secrets:

   ```bash
   npx supabase secrets set \
     FASTPIX_TOKEN_ID=<token-id> \
     FASTPIX_TOKEN_SECRET=<token-secret>
   ```

5. Push the config. This sends the `verify_jwt` settings — without it, the webhook returns `401`.

   ```bash
   npx supabase config push
   ```

6. Deploy the functions:

   ```bash
   npx supabase functions deploy
   ```

7. Create the production webhook. After you deploy, the `fastpix-webhook` URL appears in the dashboard under **Edge Functions**. Register it in FastPix, then set the signing secret:

   ```bash
   npx supabase secrets set FASTPIX_WEBHOOK_SECRET=<signing-secret>
   ```

   Secrets take effect immediately. You don't need to redeploy.

8. If your account has existing media, run `npx @fastpix/supabase backfill` against your production database.

Then secure the tables as described below.

> **NOTE:** The edge functions open a direct, non-pooled Postgres connection, and the drain cron fires every 10 seconds. On a small instance you may approach your connection limit — if you see connection errors, point the functions at Supavisor or slow the drain in `0003_fastpix_setup_cron_job.sql`.

\


## 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:

```sql
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.

\


## Query your data

For server-side queries, use the service-role key, which bypasses row-level security:

```typescript
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  "your-supabase-url",
  "your-supabase-service-role-key",
  { db: { schema: "fastpix" } },
);

const { data: media } = await supabase
  .from("media")
  .select("*")
  .eq("status", "ready");
```

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

\


## Use the sync engine with any Postgres

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

```bash
npm install @fastpix/fp-sync-engine
```

Create the schema once, then process webhooks in your own server. This example uses Express:

```typescript
import express from "express";
import { FastPixSync, runMigrations, InvalidSignatureError } from "@fastpix/fp-sync-engine";

// Creates or upgrades the `fastpix` schema. Safe to run on every boot.
await runMigrations({ databaseUrl: process.env.DATABASE_URL });

const sync = new FastPixSync({
  databaseUrl: process.env.DATABASE_URL,
  fastpixWebhookSecret: process.env.FASTPIX_WEBHOOK_SECRET,
  fastpixTokenId: process.env.FASTPIX_TOKEN_ID,
  fastpixTokenSecret: process.env.FASTPIX_TOKEN_SECRET,
});

const app = express();

// FastPix pings the URL when you register it.
app.get("/webhook", (_req, res) => res.send("ok"));

// Take the raw body — the engine verifies the signature over the exact bytes.
app.post("/webhook", express.text({ type: "*/*" }), async (req, res) => {
  try {
    const { eventId, isDuplicate } = await sync.ingestWebhook(
      req.body,
      req.headers as Record<string, string>,
    );
    res.sendStatus(202);

    // Process out of band so a slow re-fetch can't outrun FastPix's retry timer.
    if (!isDuplicate) {
      sync.processStoredEvent(eventId).catch((err) => console.error(err));
    }
  } catch (err) {
    res.sendStatus(err instanceof InvalidSignatureError ? 401 : 500);
  }
});

app.listen(3000);
```

For the full API reference, see the [`@fastpix/fp-sync-engine` README](https://github.com/FastPix/fastpix-sync-engine).

\


## Troubleshoot

**FastPix won't save the webhook URL.** It checks the URL before saving. Make sure ngrok and `functions serve` are both running, then `curl` the URL — you should get `ok`. If you get `401`, you skipped the restart after `init`.

**No rows in `fastpix.webhook_events`.** FastPix isn't reaching your webhook, or the signature is failing. Check that your ngrok URL is current, that `FASTPIX_WEBHOOK_SECRET` matches exactly, and that you restarted `functions serve` after editing `.env`.

**Rows stuck at `received`.** Nothing is draining the queue. Confirm both Vault secrets exist and hold the correct values.

**`fastpix` schema or tables don't exist.** `init` skipped database setup. Run `SUPABASE_DB_URL='postgresql://...' npx @fastpix/supabase migrate`.

**`db push` fails with "Found local migration files to be inserted before the last migration."** Re-run with `npx supabase db push --include-all`.

**The webhook returns `401` in production.** Either you didn't run `npx supabase config push`, or the signing secret is wrong — check with `npx supabase secrets list`.

**A SQL query returns a column-not-found error.** Column names are camelCase and need double quotes: `select "mediaId" from fastpix.media`.

**`Cannot connect to the Docker daemon`.** Docker isn't running. Start Docker Desktop, or on Linux run `sudo systemctl start docker`.

\


## Resources

* [`@fastpix/supabase` on GitHub](https://github.com/FastPix/fastpix-supabase)
* [`@fastpix/fp-sync-engine` on GitHub](https://github.com/FastPix/fastpix-sync-engine)
* [Set up webhooks](/docs/webhooks/set-up-webhooks)
* [FastPix API reference](/docs/platform-api/overview)
* [Supabase documentation](https://supabase.com/docs)