API Docs
Dashboard

Webhooks

Register an endpoint and we'll POST a signed event to it whenever trip, booking or invoice activity happens — so your systems stay in sync without polling.

Beta. Webhooks are rolling out. The event names and payload shapes below are stable in intent but may gain fields before general availability.

Events

Subscribe to any of these event types when you create an endpoint:

EventWhen it firesPayload data
trip.createdA trip is created (via the API, your booking pages, or an accepted quote).Trip
trip.assignedA driver (and/or car) is assigned or reassigned to a trip.Trip
trip.status_changedA trip moves through its lifecycle (confirmed, in progress, completed).Trip
trip.cancelledA trip is cancelled.Trip
booking.createdA product/service booking is created.Booking
quote.acceptedA customer accepts a quote.Quote
invoice.paidAn invoice becomes fully paid.Payment summary

Register an endpoint

Create a webhook with an https URL and the events you care about. The response includes a secret — this is the only time it's returned, so store it securely. You'll use it to verify every delivery.

Request

curl -X POST https://app.destination.dev/api/v1/webhooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks",
    "events": ["trip.created", "trip.cancelled"]
  }'

Response

{
  "data": {
    "endpoint_id": 1,
    "url": "https://example.com/webhooks",
    "events": ["trip.created", "trip.cancelled"],
    "status": "enabled",
    "secret": "whsec_182022b9888a6d2f2307..."
  },
  "meta": { "request_id": "req_1a2b3c4d5e6f7a8b" }
}

The event payload

Each delivery is a POST with a JSON body and these headers:

  • X-Destination-Event — the event type.
  • X-Destination-Delivery — the unique event id.
  • X-Destination-Signaturet=<timestamp>,v1=<signature>.

Respond with any 2xx status to acknowledge receipt.

POST body

{
  "id": "evt_d49eba5792d7699d5a5d",
  "type": "trip.created",
  "created": 1785148251,
  "data": {
    "trip_id": 4821,
    "reference": "TRIP-4821",
    "status": "confirmed",
    "pickup_at": "2026-08-02 09:15:00",
    "customer_name": "Ada Lovelace"
  },
  "meta": { "business_id": 7 }
}

Verifying signatures

Before trusting a delivery, recompute the signature and compare it. Take the t and v1 values from the X-Destination-Signature header, then compute HMAC-SHA256(t + "." + rawBody, secret) and check it equals v1. Always compare using a constant-time function.

$payload = file_get_contents('php://input');
$header  = $_SERVER['HTTP_X_DESTINATION_SIGNATURE'];
parse_str(strtr($header, ',', '&'), $p); // t=..., v1=...

$expected = hash_hmac(
    'sha256', $p['t'] . '.' . $payload, $secret
);

if (!hash_equals($expected, $p['v1'])) {
    http_response_code(400);
    exit;
}
$event = json_decode($payload, true);
const crypto = require("crypto");

// rawBody must be the exact bytes received.
const header = req.headers["x-destination-signature"];
const p = Object.fromEntries(
  header.split(",").map((kv) => kv.split("="))
);

const expected = crypto
  .createHmac("sha256", secret)
  .update(p.t + "." + rawBody)
  .digest("hex");

if (
  !crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(p.v1)
  )
) {
  return res.status(400).end();
}
const event = JSON.parse(rawBody);

Retries & delivery

If your endpoint doesn't return a 2xx, we retry with exponential backoff (roughly 1, 2, 4, 8 and 16 minutes) for up to six attempts, then mark the delivery failed. Inspect recent attempts with GET /api/v1/webhooks/{id}/deliveries.

Make your handler idempotent. A delivery may arrive more than once. De-duplicate on the event id so retries are harmless.