Paxvel Developers
DocsAPI referenceConsole Sign in Request access

Webhooks

Webhooks push booking changes to your system as soon as they happen, so you do not need to poll.

Setting up

Enter an HTTPS endpoint under Dashboard → Webhooks — one for test, one for live. The test endpoint receives events of sandbox bookings, the live endpoint of real bookings. Press Send test to receive a ping event right away.

Requirements: HTTPS on port 443, a public address, and an answer with any 2xx status within 8 seconds. Do the real processing asynchronously after answering.

Events

EventWhen
booking.createdA booking was created through the API.
booking.updatedSomething the customer can see changed: status, confirmation, driver details, time, price, payment.
booking.cancelledThe booking was cancelled (by you or by us).
pingTest event from the portal or POST /test/webhook.

Payload

POST /paxvel/webhook HTTP/1.1
Content-Type: application/json
Paxvel-Event: booking.updated
Paxvel-Delivery: 81723
Paxvel-Signature: t=1763634612,v1=5f8a3b9e2c…
{
  "id": "evt_8f2c1a9b0d3e4f5a6b7c",
  "type": "booking.updated",
  "env": "live",
  "created_at": "2026-11-19T18:30:12+00:00",
  "data": { "reference": "PXT-01234", "status": "confirmed", "items": [ … ] }
}

data is the full booking, in exactly the same format as GET /bookings/{reference}. You can simply replace your stored copy.

Verifying the signature

Every request carries Paxvel-Signature: t=<unix time>,v1=<signature>, where v1 is the hex HMAC-SHA256 of the string t + "." + raw body, keyed with your signing secret (Dashboard → Webhooks). Always verify it before trusting the content, and reject timestamps older than 5 minutes to block replays.

// PHP
[$t, $v1] = array_map(fn($p) => explode('=', $p, 2)[1], explode(',', $_SERVER['HTTP_PAXVEL_SIGNATURE']));
$body = file_get_contents('php://input');
$ok = hash_equals(hash_hmac('sha256', $t . '.' . $body, $secret), $v1) && abs(time() - (int)$t) < 300;
// Node.js (use the raw body, before JSON parsing)
const [t, v1] = req.get('Paxvel-Signature').split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) && Math.abs(Date.now() / 1000 - t) < 300;
# Python
t, v1 = [p.split("=", 1)[1] for p in request.headers["Paxvel-Signature"].split(",")]
expected = hmac.new(secret.encode(), f"{t}.".encode() + request.get_data(), hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, v1) and abs(time.time() - int(t)) < 300

Retries and ordering

If your endpoint does not answer 2xx, we retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours; then the delivery is marked failed (about 33 hours after the first attempt) and we email your technical contact. You can see every delivery and its result under Dashboard → Webhooks.

Deliveries can arrive more than once or out of order. Use the event id to ignore duplicates, and treat data as the current state rather than applying changes step by step.

Rotating the secret

Rotate and show creates a new secret and invalidates the old one immediately. Deploy the new secret to your system right after rotating.