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

# Webhook setup

> Create a webhook delivery destination and start receiving HMAC-signed mapping events on your HTTPS endpoint.

A webhook destination sends an HTTP POST to your server every time a mapping job completes or another subscribed event fires. Payloads are signed with HMAC-SHA256 so you can verify they originated from Mapping.Travel.

## Prerequisites

* An HTTPS endpoint reachable from the public internet (not `localhost` or behind a firewall).
* A place to securely store the signing secret shown once at creation time.

## Create via the dashboard

1. Open **Developers → Delivery destinations → New destination**.
2. Choose **Webhook** as the type.
3. Paste your endpoint URL and select the events to subscribe to.
4. Click **Create**. Copy the **signing secret** — it is shown only once and cannot be retrieved later.

## Create via API

```bash theme={null} theme={null}
curl -X POST https://api.mapping.travel/api/v1/delivery-destinations \
  -H "X-API-Key: <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "WEBHOOK",
    "name": "Production webhook",
    "webhookUrl": "https://your-server.example.com/hooks/mapping",
    "events": ["mapping.completed", "mapping.error.resolved"],
    "status": "ACTIVE"
  }'
```

The response includes the signing secret **in plaintext exactly once**:

```json theme={null} theme={null}
{
  "id": "dest_01HZ1A2B3C4D5E6F7G8H9J0K",
  "type": "WEBHOOK",
  "name": "Production webhook",
  "webhookUrl": "https://your-server.example.com/hooks/mapping",
  "events": ["mapping.completed", "mapping.error.resolved"],
  "status": "ACTIVE",
  "signingSecret": "whsec_e1c5d3a0b2f4…",
  "signingSecretMasked": "whsec_••••3a0e",
  "createdAt": "2026-06-07T12:00:00Z"
}
```

<Warning>
  Store the `signingSecret` in your secret manager immediately. You can rotate it later via `POST /api/v1/delivery-destinations/{id}/rotate-secret`, but you cannot read it back.
</Warning>

## Endpoint requirements

Your HTTP endpoint must:

1. Accept `POST` requests over **HTTPS** only. Plain HTTP is rejected.
2. Return a **`2xx` status code within 30 seconds**. Anything else (3xx, 4xx, 5xx, or timeout) is treated as a failure and retried.
3. **Tolerate duplicate deliveries** — deduplicate on the `eventId` field, which is stable across all retry attempts.
4. **Verify the `X-Webhook-Signature` header** on every request. See [Verifying signatures](/delivery-destinations/verifying-signatures).

## Request headers

Every delivery includes:

| Header                | Example                   | Description                       |
| --------------------- | ------------------------- | --------------------------------- |
| `X-Webhook-Signature` | `t=1717250400,v1=abc123…` | HMAC-SHA256 signature + timestamp |
| `X-Webhook-Event`     | `mapping.completed`       | The event type                    |
| `X-Webhook-Delivery`  | `del_01HZ…`               | Unique delivery attempt ID        |
| `Content-Type`        | `application/json`        | Always `application/json`         |

## Payload envelope

```json theme={null} theme={null}
{
  "id": "del_01HZ1A2B3C4D5E6F7G8H9J0K",
  "eventId": "evt_01HZ…",
  "type": "mapping.completed",
  "apiVersion": "2026-06-01",
  "createdAt": "2026-06-07T12:01:00Z",
  "data": {
    "mappingJobId": "job_01HZ…",
    "status": "COMPLETED",
    "rowCount": 4821,
    "exportFileId": "ef_01HZ…"
  }
}
```

Use `eventId` — not `id` — as your idempotency key, since `id` changes on each retry attempt.

## Verifying the signature

See the full verification guide with code in Node, Python, Kotlin, and Go: [Verifying signatures](/delivery-destinations/verifying-signatures).

Quick summary: parse `t=` and `v1=` from `X-Webhook-Signature`, reject if the timestamp is more than ±5 minutes from your clock, then verify `HMAC_SHA256(secret, "{t}.{raw_body}")` in constant time.

## Rotating the signing secret

```bash theme={null} theme={null}
curl -X POST \
  https://api.mapping.travel/api/v1/delivery-destinations/{id}/rotate-secret \
  -H "X-API-Key: <your-token>"
```

The response returns the new secret in plaintext once. During the rotation window (≈5 minutes), both the old and new secrets are accepted so you can deploy your updated secret without dropping deliveries.

## Troubleshooting

<AccordionGroup>
  <Accordion title="4xx responses — endpoint rejecting the request">
    A `4xx` means your server received the request but rejected it. Common causes:

    * **401 / 403**: Your server is checking an auth header or secret that doesn't match. Verify you're reading the signing secret from the right environment variable.
    * **404**: The path in your webhook URL doesn't exist. Check your route configuration.
    * **422 / 400**: Your server parsed the body and rejected it. Confirm you are using the raw request body (not re-parsed JSON) when verifying the signature.

    <Note>
      Mapping.Travel retries 4xx responses the same as 5xx. After 4 failed attempts the delivery is marked `DEAD`. Fix the endpoint and use the **Test** button to send a fresh payload.
    </Note>
  </Accordion>

  <Accordion title="5xx responses — server errors">
    A `5xx` means your server accepted the request but encountered an internal error. Check your application logs for exceptions. The delivery will be retried at 1m, 5m, and 30m intervals.
  </Accordion>

  <Accordion title="Network errors — no response at all">
    If Mapping.Travel cannot reach your endpoint (connection refused, DNS failure, or timeout > 30s), the delivery is retried with the same schedule. Verify:

    * The URL is publicly reachable (not behind a VPN or firewall that blocks our IPs).
    * Your TLS certificate is valid and not expired.
    * Your server is not rate-limiting our source IPs.
  </Accordion>

  <Accordion title="Signature verification failing">
    Most common cause: middleware is re-parsing the JSON body before it reaches your handler, altering the bytes used to compute the signature. Use a raw-body middleware specifically for the webhook path. See [Verifying signatures](/delivery-destinations/verifying-signatures) for framework-specific tips.
  </Accordion>
</AccordionGroup>
