Skip to main content

Overview

Webhooks allow you to receive real-time HTTP notifications when a delivery status changes. Instead of polling the API for updates, Chidori pushes status updates directly to your server.
Webhooks are configured through the Chidori Dashboard. You can set your webhook URL and manage your signing secret from the dashboard.

How webhooks work

1

Configure webhook in dashboard

Go to chidori.africa/dashboard and set your webhook URL in the settings.
2

Delivery status changes

When a delivery status changes (e.g., ASSIGNED, PICKED_UP, DELIVERED), Chidori creates a webhook payload.
3

Payload delivered

Chidori sends an HTTP POST request to your webhook URL with the event data.
4

Verify and process

Your server verifies the signature and processes the event.

Webhook event

Chidori sends webhooks for the delivery.status_changed event when a delivery transitions between statuses.

Delivery statuses

StatusDescription
PENDINGDelivery created, awaiting payment
PAIDPayment received, awaiting driver assignment
ASSIGNEDDriver assigned to the delivery
PICKED_UPDriver has picked up the package
IN_TRANSITPackage is on the way to destination
DELIVEREDPackage successfully delivered
CANCELLEDDelivery was cancelled

Webhook payload

All webhook payloads follow this structure:
{
  "id": "evt_abc123def456",
  "event": "delivery.status_changed",
  "timestamp": "2025-01-15T10:45:00.000Z",
  "data": {
    "deliveryId": "abc123-def456-ghi789",
    "trackingId": "CHI-ABC123",
    "status": "IN_TRANSIT",
    "bulk": null,
    "order": null,
    "isPaid": true,
    "updatedAt": "2025-01-15T10:45:00.000Z"
  }
}

Payload fields

id
string
Unique identifier for this webhook event
event
string
Always delivery.status_changed
timestamp
string
ISO 8601 timestamp when the event occurred
data
object

Signature verification

Every webhook request includes a signature in the X-Webhook-Signature header. Always verify this signature to ensure the request came from Chidori. The signature format is: t={timestamp},v1={signature}
const crypto = require('crypto');

function verifyWebhookSignature(payload, signatureHeader, secret) {
  const parts = signatureHeader.split(',');
  const timestampPart = parts.find(p => p.startsWith('t='));
  const signaturePart = parts.find(p => p.startsWith('v1='));
  
  if (!timestampPart || !signaturePart) return false;
  
  const timestamp = timestampPart.slice(2);
  const providedSignature = signaturePart.slice(3);
  
  // Check timestamp is within 5 minutes
  const timestampNum = parseInt(timestamp, 10);
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - timestampNum) > 300) return false;
  
  // Verify signature
  const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(providedSignature),
    Buffer.from(expectedSignature)
  );
}

// Express webhook handler
app.post('/webhooks/chidori', express.json(), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  
  if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  const { event, data } = req.body;
  console.log(`Delivery ${data.deliveryId} status: ${data.status}`);
  
  // Process asynchronously
  processStatusChange(data).catch(console.error);
  
  res.status(200).send('OK');
});
Always verify webhook signatures in production. Never trust webhook data without verification.

Best practices

Respond quickly

Return a 200 response immediately, then process the event asynchronously.

Handle duplicates

Webhooks may be retried. Use the event id to handle duplicate events.

Use HTTPS

Your webhook URL must use HTTPS for security.

Log everything

Log all webhook events for debugging and audit purposes.

Retry policy

If your webhook endpoint returns an error (non-2xx status), Chidori will retry the delivery:
  • Retry attempts: Up to 3 retries
  • Retry intervals: 1 second, 5 seconds, 30 seconds
  • Timeout: 10 seconds per request

Example: Update order status

async function processStatusChange(data) {
  const { deliveryId, trackingId, status } = data;
  
  // Find your order by delivery ID
  const order = await Order.findOne({ deliveryId });
  if (!order) return;
  
  // Update order status based on delivery status
  switch (status) {
    case 'ASSIGNED':
      await order.update({ status: 'driver_assigned' });
      await notifyCustomer(order, 'Your delivery has been assigned to a driver');
      break;
    case 'PICKED_UP':
      await order.update({ status: 'picked_up' });
      await notifyCustomer(order, 'Your package has been picked up');
      break;
    case 'IN_TRANSIT':
      await order.update({ status: 'in_transit' });
      break;
    case 'DELIVERED':
      await order.update({ status: 'delivered', deliveredAt: new Date() });
      await notifyCustomer(order, 'Your package has been delivered!');
      break;
    case 'CANCELLED':
      await order.update({ status: 'cancelled' });
      await notifyCustomer(order, 'Your delivery has been cancelled');
      break;
  }
}

Webhook configuration

Webhooks are managed through the Chidori Dashboard:

Configure Webhooks

Set your webhook URL and view your signing secret in the dashboard.