Endpoint Management

GET/api/v1/webhook-endpoints
GET/api/v1/webhook-endpoints/events
POST/api/v1/webhook-endpoints
PATCH/api/v1/webhook-endpoints/:id
DELETE/api/v1/webhook-endpoints/:id

Create accepts an HTTPS url and a non-empty events array. Accepted subscription names are shipment.created, shipment.status_changed, shipment.delivered, and shipment.exception. The current carrier-update pipeline emits shipment.status_changed; inspect payload.status for delivered or exception.

Secret and Delivery Operations

POST/api/v1/webhook-endpoints/:id/rotate-secret
GET/api/v1/webhook-endpoints/:id/deliveries
POST/api/v1/webhook-endpoints/:id/deliveries/:deliveryId/retry
POST/api/v1/webhook-endpoints/:id/test

Creation and rotation return a whsec_ signing secret once. List and update responses explicitly exclude it. Store it in a secret manager and replace it atomically when rotating. Create, update, delete, rotate, retry, and test require owner/admin role; endpoint and delivery operations verify merchant ownership.

Headers and Payload

X-OT-Timestamp: 1784010721
X-OT-Signature: 7f2a... (lowercase hex HMAC-SHA256)

{
  "event": "shipment.status_changed",
  "payload": {
    "shipmentId": "...",
    "externalOrderId": "...",
    "orderNumber": "1042",
    "trackingNumber": "1Z999AA10123456784",
    "carrierCode": "ups",
    "status": "out_for_delivery",
    "lastEvent": "Out for delivery",
    "lastEventTime": "2026-07-14T03:30:00.000Z",
    "trackingEvents": []
  }
}

Verify the Exact Raw Body

import { createHmac, timingSafeEqual } from 'node:crypto';

const expected = createHmac('sha256', process.env.OT_WEBHOOK_SECRET)
  .update(rawBody)
  .digest('hex');
const valid = signature.length === expected.length &&
  timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

Requests time out after 15 seconds. Jobs use up to five attempts with exponential backoff beginning at 30 seconds. Validate timestamps and reject replayed deliveries in your receiver.