Skip to main content
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

curl -X POST https://api.mapping.travel/api/v1/delivery-destinations \
  -H "Authorization: Bearer <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:
{
  "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"
}
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.

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.

Request headers

Every delivery includes:
HeaderExampleDescription
X-Webhook-Signaturet=1717250400,v1=abc123…HMAC-SHA256 signature + timestamp
X-Webhook-Eventmapping.completedThe event type
X-Webhook-Deliverydel_01HZ…Unique delivery attempt ID
Content-Typeapplication/jsonAlways application/json

Payload envelope

{
  "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. 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

curl -X POST \
  https://api.mapping.travel/api/v1/delivery-destinations/{id}/rotate-secret \
  -H "Authorization: Bearer <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

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.
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.
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.
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.
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 for framework-specific tips.