Overview
Webhooks notify your application when parcel events occur in real-time, eliminating the need to poll for updates.
Setup
- Go to Dashboard → Settings → Webhooks
- Add your endpoint URL (must be HTTPS)
- Select the events you want to receive
- Save and test the webhook
Configure separate URLs for sandbox and production environments.
All webhooks follow this structure:
{
"id": "evt_abc123xyz",
"event": "parcel.status_changed",
"timestamp": "2024-12-21T14:30:00Z",
"environment": "production",
"data": {
// Event-specific payload
}
}
Event Types
parcel.status_changed
Triggered when a parcel’s status changes.
{
"event": "parcel.status_changed",
"data": {
"trackingNumber": "CBECUSD123456789",
"previousStatus": "IN_TRANSIT",
"newStatus": "OUT_FOR_DELIVERY",
"timestamp": "2024-12-21T14:30:00Z",
"location": "Guayaquil, EC"
}
}
parcel.delivered
Triggered when a parcel is delivered.
{
"event": "parcel.delivered",
"data": {
"trackingNumber": "CBECUSD123456789",
"deliveredAt": "2024-12-21T16:45:00Z",
"signature": "M. García",
"location": "Guayaquil, EC",
"proofOfDelivery": "https://..."
}
}
parcel.exception
Triggered when a delivery exception occurs.
{
"event": "parcel.exception",
"data": {
"trackingNumber": "CBECUSD123456789",
"exceptionType": "DELIVERY_FAILED",
"reason": "Recipient not available",
"timestamp": "2024-12-21T17:00:00Z",
"location": "Guayaquil, EC",
"nextAttempt": "2024-12-22T09:00:00Z"
}
}
manifest.submitted
Triggered when a manifest is successfully submitted.
{
"event": "manifest.submitted",
"data": {
"shipmentId": "shp_abc123",
"awbNumber": "180-12345678",
"parcelCount": 50,
"country": "EC"
}
}
Webhook Handling
Basic Handler
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/webhooks/crossborderly', express.json(), (req, res) => {
// Verify signature
const signature = req.headers['x-crossborderly-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Invalid signature');
}
// Process event
const event = req.body;
switch (event.event) {
case 'parcel.status_changed':
handleStatusChange(event.data);
break;
case 'parcel.delivered':
handleDelivery(event.data);
break;
case 'parcel.exception':
handleException(event.data);
break;
}
// Always respond 200 quickly
res.status(200).json({ received: true });
});
function verifySignature(payload, signature) {
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Async Processing
Process webhooks asynchronously to respond quickly:
app.post('/webhooks/crossborderly', express.json(), async (req, res) => {
// Respond immediately
res.status(200).json({ received: true });
// Process in background
try {
await processWebhook(req.body);
} catch (error) {
console.error('Webhook processing failed:', error);
// Add to retry queue
await queue.add('webhook-retry', req.body);
}
});
Security
Signature Verification
All webhooks include a signature header for verification:
X-CrossBorderly-Signature: sha256=abc123...
Always verify this signature to ensure the webhook is authentic.
IP Whitelist
Webhooks are sent from these IP addresses:
34.198.12.45
52.44.78.123
18.215.99.67
Optionally whitelist these IPs in your firewall.
Retry Policy
| Attempt | Delay |
|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
| Final | 24 hours |
After all retries fail, the webhook is marked as failed. Check the dashboard for failed webhooks.
Best Practices
Return 200 within 5 seconds. Process asynchronously if needed.
Use the id field to deduplicate webhooks. Same event may be sent multiple times.
Always verify the webhook signature to prevent spoofing.
Store raw webhook payloads for debugging and audit purposes.
Testing
Send test webhooks from the dashboard:
- Go to Webhooks → Test
- Select event type
- Click Send Test Webhook
Or use the API:
curl -X POST https://api.crbtrack.com/api/v1/webhooks/test \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"event": "parcel.delivered", "trackingNumber": "CBECUSD123456789"}'