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
| Event | When |
|---|---|
booking.created | A booking was created through the API. |
booking.updated | Something the customer can see changed: status, confirmation, driver details, time, price, payment. |
booking.cancelled | The booking was cancelled (by you or by us). |
ping | Test 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)) < 300Retries 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.