Skip to main content

Webhooks

Webhooks deliver real-time event notifications to your application via HTTP POST requests. Instead of polling the API, your server receives updates the moment something happens.

How webhooks work

Supported events

EventDescriptionFired when
tracking.updatedTracking status changedAny status transition (accepted, in_transit, out_for_delivery)
tracking.deliveredPackage deliveredFinal delivery confirmation
tracking.exceptionDelivery exception occurredFailed attempt, hold, damage, or customs issue
label.createdNew label purchasedPOST /v1/labels succeeds
label.cancelledLabel voidedPOST /v1/labels/:id/cancel succeeds
shipment.rate_availableRate quote completedAsync rate request finishes

Registering a webhook

curl -X POST https://api.flexops.io/v1/webhooks \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/flexops",
"events": ["tracking.updated", "tracking.delivered"],
"secret": "whsec_your_signing_secret"
}'
tip

Use a unique secret per webhook endpoint. This secret is used to generate HMAC signatures so your server can verify that payloads are authentic.

Webhook payload

{
"id": "evt_abc123",
"type": "tracking.updated",
"createdAt": "2026-03-10T14:30:00Z",
"data": {
"trackingNumber": "9400111899223456789012",
"carrier": "usps",
"status": "in_transit",
"previousStatus": "accepted",
"estimatedDelivery": "2026-03-12T17:00:00Z"
}
}

Verifying webhook signatures

Every webhook includes an X-FlexOps-Signature header containing an HMAC-SHA256 signature. Always verify this signature before processing the payload.

import crypto from 'crypto';

function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

// In your Express handler:
app.post('/webhooks/flexops', (req, res) => {
const isValid = verifyWebhook(
req.body,
req.headers['x-flexops-signature'],
process.env.FLEXOPS_WEBHOOK_SECRET
);
if (!isValid) return res.status(401).send('Invalid signature');

const event = JSON.parse(req.body);
// Process event...
res.status(200).send('OK');
});

Retry policy

Failed webhook deliveries (non-2xx response or timeout) are retried with exponential backoff:

AttemptDelayCumulative time
1Immediate0
21 minute1 min
35 minutes6 min
430 minutes36 min
52 hours2 hr 36 min
612 hours14 hr 36 min

After 6 failed attempts, the webhook endpoint is disabled. Re-enable it from the Dashboard or via POST /v1/webhooks/:id/enable.

warning

Your webhook endpoint must respond within 30 seconds. Process events asynchronously — acknowledge the webhook immediately with 200 OK, then handle the business logic in a background job.

Best practices

  1. Always verify signatures — Never trust webhook payloads without HMAC verification
  2. Respond quickly — Return 200 OK before processing; use a queue for heavy work
  3. Handle duplicates — Use the id field to deduplicate; the same event may be delivered more than once
  4. Monitor delivery — Check webhook delivery status in the Dashboard under Settings > Webhooks > Delivery Log
  5. Use HTTPS — Webhook URLs must use HTTPS in production (HTTP allowed in sandbox)