# Endpoints & webhooks

URL: /docs/plugins/payload-mux/endpoints-and-webhooks

Finish long-encoding videos, sync Mux-dashboard deletes, and ingest video in code — the endpoints and webhook that keep Payload and Mux in step.

The plugin mounts three HTTP endpoints and expects one Mux webhook. Together they finish slow-encoding videos, mirror Mux-dashboard deletes back into Payload, and let you create a video entirely in code.

## Endpoints

The plugin registers three endpoints under Payload's API route. The upload pair is gated by the `options.access.upload` option.

| Endpoint                   | What it does                                                                                                                                                     |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /api/mux/upload`     | Mint a Mux direct-upload. The admin uploader posts files to it.                                                                                                  |
| `GET /api/mux/upload?id=…` | Read a direct-upload, to pick up its `asset_id`.                                                                                                                 |
| `POST /api/mux/webhook`    | Verify and apply Mux events: set metadata on ready/updated, delete on deleted, log on errored, and (with `autoCreateOnWebhook`) backfill assets uploaded in Mux. |

## Webhook

On upload, the `beforeChange` hook polls the new asset for **\~6 seconds**. If Mux finishes encoding in that window, the hook fills in the playback metadata inline. Anything slower is saved with just its `assetId` and left for the webhook.

Point a Mux webhook at `/api/mux/webhook` (or `<your routes.api>/mux/webhook` if you've customized Payload's API route). Set `MUX_WEBHOOK_SECRET` so the plugin can verify each event's signature.

> **The webhook needs a publicly reachable URL.** Mux *pushes* events to your endpoint, so its servers
> must be able to POST to it. The SDK only verifies the signature on receipt — it can't make Mux reach
> `localhost`. In production, set `MUX_WEBHOOK_SECRET` and point the dashboard at
> `https://your-site/api/mux/webhook`. For local dev, expose localhost with a tunnel
> (`cloudflared tunnel --url http://localhost:3000` or ngrok) and set that URL as the webhook.

Not every flow depends on the webhook. Use the split below to know what breaks without it.

**Needs the webhook**

- **Metadata for videos that take longer than \~6s to encode, without anyone touching the doc.** The
  common case. The upload's poll gives up at \~6s and the webhook is what fills `playbackOptions` in
  on its own. Without it the video stays `preparing` until someone saves the doc, which refetches
  from Mux — so a missed event is recoverable, but only by hand. See
  [Troubleshooting](/docs/plugins/payload-mux/troubleshooting).
- **Mux → Payload delete sync.** Deleting an asset in the Mux dashboard removes the Payload doc only
  via the `video.asset.deleted` event.
- **`autoCreateOnWebhook` backfill.** Importing assets you uploaded directly in Mux.

**Works without it**

- The upload itself, and metadata for short videos (set synchronously by the 6s poll).
- **Recovering a video stuck on `preparing`.** Saving the doc refetches the asset from Mux, so a
  webhook you never received doesn't strand the video.
- **Payload → Mux delete.** Handled by the collection's `afterDelete` hook, not the webhook.
- **Server-side ingest / seeding.** The 6s limit doesn't apply; ingest waits for `ready` and writes
  full metadata into the doc, so these play straight away whatever their length.
- Playback of already-populated videos: the URLs compute on read from the stored playback id.

## Server-side ingest

Besides the browser uploader, a `mux-video` can be created **server-side** from a local file or URL. This is handy for imports, migrations, and seeding. Pass a `source`; the `beforeValidate` hook uploads it to Mux, waits for the asset to be ready, fills in the metadata, and discards `source`.

```ts
import { ingestMuxVideo } from '@pro-laico/payload-mux'

await ingestMuxVideo(payload, { source: '/path/to/intro.mp4', title: 'Intro' })
// or a URL: ingestMuxVideo(payload, { source: 'https://example.com/intro.mp4', title: 'Intro' })
// playback policy comes from the plugin's `options.playbackPolicy`; pass { playbackPolicy: 'signed' } to override one video
// posterTimestamp: 12.5 picks the poster frame; defaults to the middle of the video
// collection: 'media' — only if you renamed the collection AND `payload` was built without the plugin; otherwise the marker's slug is used
```

Local paths stream from disk, so ingesting a large file doesn't load it all into memory. Ingest is also what powers declarative seeding — see [How it works](/docs/plugins/payload-mux/how-it-works#seeding).

## Next

- [Use a video](/docs/plugins/payload-mux/using-video) — relate, play back, and sign playback URLs.
- [How it works](/docs/plugins/payload-mux/how-it-works) — the asset lifecycle, revalidation, and seeding.
- [Troubleshooting](/docs/plugins/payload-mux/troubleshooting) — videos stuck on "preparing" and other fixes.
