Guides
Verify webhook signatures
Authenticate exact bytes, acknowledge quickly and deduplicate by event ID.
Signed payload helpers and retry simulation are implemented. The sandbox makes no deliveries. A live worker and encrypted signing-secret store are not released.
Signature contract
PX-Signature: t=1791109500,v1=hex_hmac_sha256
signed bytes = ASCII(timestamp) + "." + exact raw UTF-8 bodyUse HMAC-SHA256 and the endpoint signing secret, compare in constant time, and reject signed timestamps more than 300 seconds before or after your clock. Verify before parsing JSON. Do not serialize a parsed object to reconstruct the signed bytes. Multiple v1 signatures may be accepted during future secret rotation; require exactly one valid timestamp.
Node.js verification
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(secret, rawBody, header) {
const parts = String(header).split(',');
const times = parts.filter(p => p.startsWith('t='));
if (times.length !== 1 || !/^t=\d+$/.test(times[0])) return false;
const timestamp = Number(times[0].slice(2));
if (!Number.isSafeInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac('sha256', secret).update(timestamp + '.').update(rawBody).digest();
return parts.filter(p => /^v1=[a-f0-9]{64}$/.test(p)).some(p => {
const actual = Buffer.from(p.slice(3), 'hex');
return actual.length === expected.length && timingSafeEqual(actual, expected);
});
}Delivery and retries
The unreleased worker enforces a 5-second timeout. Accept durably, return a 2xx response, then process asynchronously. Non-2xx and timeouts retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours relative to the preceding attempt. Seven attempts including the first; exhausted events go to a dead-letter queue. The local simulator and new persistent retry functions implement this schedule; real delivery is disabled and no live retry SLA is available.
Duplicate and out-of-order events
Use a unique database constraint on event.id and apply your change in the same transaction as marking the event processed. A retry retains its event ID; signature timestamp can change per attempt. Event creation time is not a global ordering guarantee. Fetch current shipment state when ordering matters. HTTPS and public DNS are required; live PX will resolve and block private addresses at each attempt and disallow redirects.
Events
shipment.created, shipment.booked, shipment.cancelled, shipment.failed, tracking.updated, shipment.in_transit, shipment.out_for_delivery, shipment.delivered and shipment.exception are the planned event catalogue. The sandbox automatically queues only created, booked and cancelled. An unapplied database trigger now connects canonical tracking inserts and API shipment lifecycle writes to the private outbox. No customer delivery is enabled.