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

# Inbound webhook dry-run rollout

> Test inbound webhook mappings safely before live transactional sends are enabled.

Inbound webhooks are intentionally conservative. New configurations are disabled and dry-run by default, and live promotion is server-locked until a run-log verified promotion flow lands.

Use this guide to validate a webhook integration safely today and understand what will change when live promotion is enabled.

## Current behavior

| Stage                                       | Supported today                                     | Behavior                                                                                                                                         |
| ------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Create config                               | Yes                                                 | Creates a disabled, dry-run config and reveals the signing secret once.                                                                          |
| Guided preview                              | Yes                                                 | Resolves sample payload mappings in the browser without sending a message.                                                                       |
| Signed request to Settings-created endpoint | Not yet                                             | Settings-created configs are disabled; signed requests return `404 webhook_not_found_or_disabled` until verified promotion controls are shipped. |
| Live send from inbound webhook              | Foundation exists, promotion locked in Settings/API | Runtime execution is implemented for enabled configs, but Settings/API promotion remains locked until verified promotion controls are shipped.   |
| Replay/promotion UI                         | Future work                                         | Tracked separately under webhook observability and rollout controls.                                                                             |

<Info>
  If you need production sends immediately, use the direct [Transactional template sends](/api-reference/transactional-template-sends) API with an API key and idempotency key. Use inbound webhooks today to validate mapping in the browser preview and to prepare signing code for future server-side dry-run promotion.
</Info>

## Step 1: Create the webhook

1. Open **Settings → Integrations → Webhooks & API**.
2. Click **Create webhook**.
3. Select an approved Utility template.
4. Map the required fields.
5. Paste a sample payload.
6. Click **Run dry-run preview**.
7. Create the webhook only after the preview passes.

Switchbord shows the signing secret once. Copy it immediately and store it in the external system's secret store.

## Step 2: Prepare signed requests

You can prepare the external sender now, but Settings-created webhook configs are disabled today. A request to the generated URL will return `404 webhook_not_found_or_disabled` until verified promotion controls are shipped. Use the exact raw JSON body when generating the signature.

```ts theme={null}
import { createHash, createHmac } from "node:crypto";

const payload = {
  customer: { phone: "+393331234567", first_name: "Giulia" },
  receipt: {
    id: "payment-receipt-R-1001",
    number: "R-1001",
    amount: "€ 240,00",
    pdf: "https://cdn.example.com/receipts/R-1001.pdf"
  }
};

const rawBody = JSON.stringify(payload);
const timestamp = Math.floor(Date.now() / 1000);
const secretHash = createHash("sha256").update(webhookSecret).digest("hex");
const digest = createHmac("sha256", secretHash)
  .update(`${timestamp}.${rawBody}`)
  .digest("hex");

await fetch(webhookUrl, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Switchbord-Signature": `t=${timestamp},v1=${digest}`,
    "Idempotency-Key": payload.receipt.id
  },
  body: rawBody
});
```

## Step 3: Inspect the browser preview today

Today, the supported validation step is the builder's browser preview. A healthy preview:

* resolves recipient, template, body variables, buttons, and document headers
* rejects invalid JSON samples
* rejects unsafe mapping targets
* rejects non-HTTPS document links
* does not send a WhatsApp message

When run-log verified promotion ships, signed dry-run requests will add server-side run logs with `signatureValid: true`, status `dry_run`, sanitized mapped-request diagnostics, and no raw customer payload storage.

## Step 4: Move to live sends

Until live promotion is shipped in Settings/API, use the direct API for production sends:

```text theme={null}
POST /api/v1/template-sends
```

When live promotion lands, the expected promotion checklist will be:

1. At least one recent signed dry-run for the config has `signatureValid: true`.
2. The dry-run resolved all required mappings.
3. The selected template is still approved Utility.
4. The operator has developer/admin access.
5. The config-level and workspace-level kill switches allow execution.
6. Run logs remain sanitized.

## Common rollout failures

| Failure                         | Meaning                                                             | Fix                                                                                   |
| ------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `missing_timestamp`             | Signature header lacks `t=`.                                        | Add Unix seconds timestamp to `X-Switchbord-Signature`.                               |
| `expired_timestamp`             | Sender clock outside the five-minute replay window.                 | Sync clock with NTP and sign immediately before sending.                              |
| `invalid_signature`             | Digest does not match raw body.                                     | Sign the exact raw request body bytes.                                                |
| `webhook_not_found_or_disabled` | The Settings-created webhook config is still disabled.              | Use browser preview today; use direct API for production sends until promotion ships. |
| `invalid_mapping_target`        | Mapping target includes a forbidden path segment.                   | Remove `__proto__`, `prototype`, or `constructor` from mapping keys.                  |
| `invalid_media_link`            | Document/image URL is invalid or not HTTPS.                         | Use a public HTTPS URL.                                                               |
| `template_not_approved_utility` | Selected template is missing, hidden, not approved, or not Utility. | Sync/approve a Utility template in the workspace.                                     |

## Related guides

* [Payment receipt link via URL button](/guides/receipt-link-webhook)
* [PDF receipt via DOCUMENT header](/guides/pdf-receipt-webhook)
* [Inbound webhooks](/api-reference/inbound-webhooks)
