> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crbtrack.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive real-time notifications for parcel events

## Overview

Webhooks notify your application when parcel events occur in real-time, eliminating the need to poll for updates.

## Setup

1. Go to **Dashboard** → **Settings** → **Webhooks**
2. Add your endpoint URL (must be HTTPS)
3. Select the events you want to receive
4. Save and test the webhook

<Note>
  Configure separate URLs for sandbox and production environments.
</Note>

## Webhook Format

All webhooks follow this structure:

```json theme={null}
{
  "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.

```json theme={null}
{
  "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.

```json theme={null}
{
  "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.

```json theme={null}
{
  "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.

```json theme={null}
{
  "event": "manifest.submitted",
  "data": {
    "shipmentId": "shp_abc123",
    "awbNumber": "180-12345678",
    "parcelCount": 50,
    "country": "EC"
  }
}
```

## Webhook Handling

### Basic Handler

```javascript theme={null}
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:

```javascript theme={null}
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

<AccordionGroup>
  <Accordion title="Respond Quickly">
    Return 200 within 5 seconds. Process asynchronously if needed.
  </Accordion>

  <Accordion title="Handle Duplicates">
    Use the `id` field to deduplicate webhooks. Same event may be sent multiple times.
  </Accordion>

  <Accordion title="Verify Signatures">
    Always verify the webhook signature to prevent spoofing.
  </Accordion>

  <Accordion title="Log Everything">
    Store raw webhook payloads for debugging and audit purposes.
  </Accordion>
</AccordionGroup>

## Testing

Send test webhooks from the dashboard:

1. Go to **Webhooks** → **Test**
2. Select event type
3. Click **Send Test Webhook**

Or use the API:

```bash theme={null}
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"}'
```
