> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beyondwords.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

Webhooks let your server know when [audio](/docs-and-guides/content/audio) and [video](/docs-and-guides/content/video) generation finishes, fails, or when a content item is deleted—without polling the [API](/docs-and-guides/integrations/api-overview). BeyondWords sends a `POST` request with a JSON payload to a URL you configure.

Use webhooks to trigger downstream workflows: notify a CMS, update a search index, fetch player data for a native app, or sync content across systems. See the [Pugpig integration guide](/docs-and-guides/integrations/publishing-platforms/pugpig) for a common app-distribution pattern.

<Tip>
  Set a `source_id` when creating content via the [API](/docs-and-guides/integrations/api-overview), [WordPress plugin](/docs-and-guides/integrations/publishing-platforms/wordpress), or [RSS Feed Importer](/docs-and-guides/integrations/rss-feed-importer). The webhook payload includes `source_id`, so you can map notifications back to your CMS article ID without storing BeyondWords `content_id` values.
</Tip>

<Card title="Webhooks API" icon="code" href="/api-reference/webhooks/list">
  Create, update, test, and manage webhooks programmatically.
</Card>

## How it works

1. Content is created or updated in BeyondWords—via the API, WordPress, Ghost, Magic Embed, RSS Feed Importer, or the dashboard.
2. BeyondWords processes the content (audio/video generation).
3. When processing completes, fails, or the content item is deleted, BeyondWords sends a webhook notification to each **enabled** webhook URL in your project.
4. Your endpoint receives a JSON payload and responds with a `2xx` status code.

BeyondWords retries failed deliveries automatically. If your endpoint returns `4xx` or `5xx`, or the request times out, BeyondWords will retry with exponential backoff.

## Event types

| `action_type`                    | When it fires                                     |
| -------------------------------- | ------------------------------------------------- |
| [`audio.updated`](#audioupdated) | Generation or regeneration completed successfully |
| [`audio.error`](#audioerror)     | Generation failed                                 |
| [`audio.deleted`](#audiodeleted) | Content item deleted                              |

<Warning>
  Event names reference audio, but they apply to content items as a whole. A content item may include audio and/or video outputs, and may include article and script variants. Use `action_type` to determine what happened, then inspect `state`, `media`, and `published` in the payload.
</Warning>

## Set up a webhook

<Steps>
  <Step title="Create a new webhook">
    Go to **Settings → Webhooks** in your project dashboard.

    Click **+ Webhook**.
  </Step>

  <Step title="Enter webhook URL">
    Enter the HTTPS URL where BeyondWords should send event notifications. This should be an endpoint on your server that accepts `POST` requests with a JSON body.
  </Step>

  <Step title="Set request headers (optional)">
    For paywalled or protected content, you may need to provide authentication headers to grant BeyondWords servers access to your content:

    1. Add a **Header name** and **Header value**
    2. Click **+** to add additional headers if needed
    3. Click **Save changes**

    <Info>
      Requests will be made with `User-Agent: BeyondWords Importer`
    </Info>
  </Step>

  <Step title="Enable static IP (optional)">
    If your endpoint requires IP allowlisting, enable static IP so webhook requests originate from a fixed address.

    1. Switch **Static IP** on
    2. Allowlist the displayed IP addresses in your firewall, CDN, or server configuration

    <Info>
      Webhook requests are sent from `20.234.8.180` when static IP is enabled.
    </Info>
  </Step>

  <Step title="Enable and save">
    Toggle **Enabled** on, then click **Save changes**.
  </Step>
</Steps>

## Test your webhook

### Quick preview with Webhook.site

If you want to see what a webhook payload looks like without building an endpoint, use [Webhook.site](https://webhook.site/). It gives you a temporary URL that logs any requests sent to it.

1. Open [webhook.site](https://webhook.site/) and copy your unique URL
2. In BeyondWords, go to **Settings → Webhooks** and paste that URL into **Webhook URL**
3. Click **Test**, or publish content and wait for a real `audio.updated` event
4. Return to Webhook.site to inspect the JSON payload, headers, and timestamp

Webhook.site URLs are public and temporary—use them for testing only, not production.

### Test from the dashboard

Before going live with your own endpoint:

1. Configure your **Webhook URL** and optional headers
2. Click **Test**

BeyondWords sends a sample payload to your URL. A successful test means your endpoint returned a `2xx` response. You can also [test a webhook via the API](/api-reference/webhooks/test).

## Payload reference

Each notification is a `POST` request with `Content-Type: application/json`. The event type is in `action_type`.

### Common fields

| Field                    | Description                                                                        |
| ------------------------ | ---------------------------------------------------------------------------------- |
| `action_type`            | Event type: `audio.updated`, `audio.error`, or `audio.deleted`                     |
| `state`                  | Processing state at the time of the event—`processed` or `error`                   |
| `id`                     | Numeric content ID (legacy)                                                        |
| `content_id`             | Content UUID—use with the [player API](/api-reference/player/show)                 |
| `source_id`              | Your external ID for the content (e.g. CMS article ID)—same value as `external_id` |
| `external_id`            | Same as `source_id`                                                                |
| `project_id`             | Your BeyondWords project ID                                                        |
| `title`                  | Content title                                                                      |
| `published`              | Whether the content is published in BeyondWords                                    |
| `published_at`           | Publish timestamp (ISO 8601)                                                       |
| `processing_at`          | Processing timestamp (ISO 8601)                                                    |
| `deleted`                | Whether the content item has been deleted                                          |
| `metadata`               | Custom metadata object                                                             |
| `media`                  | Array of generated media objects (`id`, `content_type`, `url`, `duration`)         |
| `audio_with_intro_outro` | Intro/outro media variants, when configured                                        |
| `image_url`              | Featured image URL, if set                                                         |
| `is_copy`                | Whether this content item is a duplicate of another                                |

After receiving a webhook, fetch full player data with the [player API](/api-reference/player/show-2) using `source_id` or [get player by content ID](/api-reference/player/show) using `content_id`.

### audio.updated

Triggered when generation or regeneration completes successfully.

```javascript audio.updated payload expandable theme={null}
{
  "id": 12345,
  "title": "My article",
  "project_id": "67890",
  "external_id": "cms-article-001",
  "state": "processed",
  "metadata": {},
  "media": [
    {
      "id": "media-uuid-1",
      "content_type": "mp3",
      "url": "https://example.com/audio.mp3",
      "duration": 281
    },
    {
      "id": "media-uuid-2",
      "content_type": "m3u8",
      "url": "https://example.com/audio.m3u8",
      "duration": 281
    }
  ],
  "audio_with_intro_outro": [],
  "image_url": null,
  "deleted": false,
  "access_key": null,
  "processing_at": "2025-07-02T13:19:39.054Z",
  "published": true,
  "published_at": "2025-07-02T13:19:38.699Z",
  "content_id": "content-uuid",
  "source_id": "cms-article-001",
  "is_copy": false,
  "action_type": "audio.updated"
}
```

### audio.error

Triggered when generation fails.

```javascript audio.error payload expandable theme={null}
{
  "id": 12345,
  "title": "My article",
  "project_id": "67890",
  "external_id": "cms-article-001",
  "state": "error",
  "metadata": {},
  "media": [],
  "audio_with_intro_outro": [],
  "image_url": null,
  "deleted": false,
  "access_key": null,
  "processing_at": "2025-07-02T13:19:39.054Z",
  "published": false,
  "published_at": "2025-07-02T13:19:38.699Z",
  "content_id": "content-uuid",
  "source_id": "cms-article-001",
  "is_copy": false,
  "action_type": "audio.error"
}
```

### audio.deleted

Triggered when a content item is deleted.

```javascript audio.deleted payload expandable theme={null}
{
  "id": 12345,
  "title": null,
  "project_id": "67890",
  "external_id": "cms-article-001",
  "state": "processed",
  "metadata": {},
  "media": [],
  "audio_with_intro_outro": [],
  "image_url": null,
  "deleted": true,
  "access_key": null,
  "processing_at": "2025-07-02T13:19:39.054Z",
  "published": true,
  "published_at": "2025-07-02T13:19:38.699Z",
  "content_id": "content-uuid",
  "source_id": "cms-article-001",
  "is_copy": false,
  "action_type": "audio.deleted"
}
```

## Managing webhooks

You can create multiple webhooks per project—for example, one for your CMS and one for a partner integration. For each webhook you can:

* Edit the URL, headers, or static IP settings
* Temporarily disable it with the **Enabled** toggle
* Delete it when no longer needed
* View delivery status and errors in the dashboard

Manage webhooks programmatically via the [Webhooks API](/api-reference/webhooks/list).

## Security and reliability

* **Use HTTPS**—webhook URLs must be served over HTTPS.
* **Authenticate requests**—configure request headers (such as `Authorization`) in the webhook settings. Validate these on your server before processing the payload. BeyondWords does not sign payloads with a shared secret—authentication is via the headers you configure.
* **Allowlist static IPs**—if your endpoint is behind a firewall, enable static IP and allowlist the BeyondWords egress addresses.
* **Respond quickly**—return a `2xx` response promptly. Process the payload asynchronously if needed.
* **Handle retries**—failed deliveries are retried automatically. Make your handler **idempotent** so duplicate notifications for the same `content_id` and `action_type` do not cause problems.
* **Map content reliably**—prefer `source_id` over `title` or URLs, which can change.

## FAQs

<AccordionGroup>
  <Accordion title="When does audio.updated fire?">
    `audio.updated` fires when audio/video generation or regeneration completes successfully—when the content item reaches the `processed` state. It does not fire when generation starts, only when it finishes (or fails, in which case `audio.error` fires instead).
  </Accordion>

  <Accordion title="How do I map a webhook back to my CMS article?">
    Use `source_id` in the payload—this is the external ID you set when creating content (your CMS article ID, WordPress post ID, RSS `guid`, etc.). If you did not set a `source_id`, use `content_id` and store the mapping in your system, or look up content in the BeyondWords dashboard.
  </Accordion>

  <Accordion title="What should my endpoint return?">
    Return any `2xx` HTTP status code to acknowledge receipt. Responses with `4xx` or `5xx` status codes, or timeouts, are treated as failures and will be retried.
  </Accordion>

  <Accordion title="Do webhooks include video?">
    Yes. Although event names reference audio, payloads describe the whole content item. Check the `media` array for generated assets. For full player configuration including video, use the [player API](/api-reference/player/show-2) after receiving the webhook.
  </Accordion>

  <Accordion title="Can I use webhooks with Magic Embed or WordPress?">
    Yes. Any integration that creates content in BeyondWords can trigger webhooks when processing completes. Set a stable `source_id` in your embed code or CMS integration so webhook payloads map cleanly to your articles. See [Magic Embed](/docs-and-guides/integrations/magic-embed) and [WordPress](/docs-and-guides/integrations/publishing-platforms/wordpress).
  </Accordion>

  <Accordion title="How are webhooks different from polling the API?">
    Webhooks push notifications to your server when events occur. Polling requires your server to repeatedly call the [content API](/api-reference/content/list) or [player API](/api-reference/player/show-2) to check whether processing has finished. Webhooks are more efficient for workflows that need to react promptly when generation completes.
  </Accordion>
</AccordionGroup>

## Getting help

If you encounter issues or have questions, [contact support](/docs-and-guides/support/get-support).
