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

> Configure HMAC-protected inbound webhooks that map external events to transactional template sends.

Inbound webhooks let a workspace receive events from external systems and map them into Switchbord's transactional template-send contract.

## Safety defaults

New webhook configurations are disabled and dry-run by default. A dry-run records the inbound run, verifies the signature, validates the mapping, and stores the mapped transactional request without queuing a live send.

Secrets are generated once. Switchbord stores only a SHA-256 hash and a short prefix for display. List responses never include the raw secret. After creation, never send the raw secret to Switchbord; use it only to produce request signatures.

## Endpoint

Each configuration gets a generated URL:

```text theme={null}
POST /api/v1/inbound-webhooks/{slug}
```

The endpoint resolves the workspace and webhook configuration from the URL slug. Payload fields are never trusted for tenancy resolution.

## Authentication

Requests must include a timestamped HMAC signature over the exact raw request body. Do not include the raw webhook secret in headers, query parameters, or the payload.

```text theme={null}
X-Switchbord-Signature: t=<unix timestamp seconds>,v1=<hex hmac sha256>
Idempotency-Key: optional-external-event-id
```

The `v1` HMAC signs `{timestamp}.{rawBody}` with the one-time secret generated when the webhook was created. Timestamps must be within five minutes of Switchbord's clock to reduce replay risk. Multiple `v1` entries are accepted so senders can retry during key rotation, but at least one must match.

Example signature generation:

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

const timestamp = Math.floor(Date.now() / 1000);
const secretHash = createHash("sha256").update(secret).digest("hex");
const digest = createHmac("sha256", secretHash)
  .update(`${timestamp}.${rawBody}`)
  .digest("hex");
const signature = `t=${timestamp},v1=${digest}`;
```

Signature verification uses a timing-safe comparison and happens before mapping or queueing.

## Mapping

The Settings builder is the recommended way to create mappings. It loads approved Utility templates, shows the body/button/header variables each template needs, and lets you map each field from a payload path, literal, or JSON value.

Runtime mappings support legacy dot-path values and explicit expressions:

```json theme={null}
{
  "to": "path:customer.phone",
  "templateName": "literal:payment_receipt_document",
  "templateLocale": "literal:it",
  "externalId": "path:receipt.id",
  "parameters.header.document.link": "path:receipt.pdf",
  "parameters.header.document.filename": "literal:receipt.pdf",
  "parameters.body.1": "path:customer.first_name",
  "parameters.body.2": "path:receipt.number"
}
```

Expression prefixes:

| Prefix     | Meaning                                           |
| ---------- | ------------------------------------------------- |
| `path:`    | Resolve a dot path from the inbound JSON payload. |
| `literal:` | Use the text after the prefix as the value.       |
| `json:`    | Parse the text after the prefix as JSON.          |
| no prefix  | Legacy dot-path lookup from the inbound payload.  |

Mapping targets may not contain `__proto__`, `prototype`, or `constructor`. Switchbord rejects unsafe targets before previewing or processing the webhook.

Configured `templateName` and `templateLocale` values override mapped values. New configs created through Settings are restricted to the selected approved Utility template.

## Run logs

Every received request for an enabled config records a run with redacted headers, payload shape metadata, sanitized mapped-request diagnostics, status, and errors. Raw customer payloads, recipient phone numbers, and template variable values are not stored in run logs by default. Invalid signatures are recorded as failed runs and return `401`, but they do not reserve verified idempotency keys.

Possible run statuses include `verified`, `dry_run`, `queued`, `failed`, and `skipped_duplicate`.

## Current limitation

The runtime foundation can execute the transactional template-send service when a config is enabled, but Settings-created configs are currently forced to disabled dry-run mode and cannot be promoted through the public Settings API. A signed request to a disabled config returns `404 webhook_not_found_or_disabled`. Live promotion is server-locked until a run-log verified promotion flow ships. Use [Transactional template sends](/api-reference/transactional-template-sends) for production sends today.

## Integration guides

* [Payment receipt link via URL button](/guides/receipt-link-webhook)
* [PDF receipt via DOCUMENT header](/guides/pdf-receipt-webhook)
* [Inbound webhook dry-run rollout](/guides/webhook-dry-run-rollout)
