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

# Webhooks

> Receive real-time notifications for delivery session lifecycle events

## Overview

Webhooks allow you to receive real-time HTTP POST notifications when delivery sessions start, stop, and when Desmo generates insights. Instead of polling our API, configure a webhook endpoint to receive updates automatically.

<Info>
  Webhooks are the recommended way to integrate Desmo into your backend systems. They provide real-time notifications for every delivery across your entire fleet.
</Info>

## Events

Desmo sends three types of webhook events:

| Event             | When it fires                         | Use case                               |
| ----------------- | ------------------------------------- | -------------------------------------- |
| `session.started` | Driver starts a delivery session      | Track active deliveries in your system |
| `session.stopped` | Driver ends a delivery session        | Mark delivery as complete              |
| `insight.updated` | Processing generates/updates insights | Get fraud scores, verification data    |

## How It Works

```mermaid theme={null}
sequenceDiagram
    participant App as Your App
    participant SDK as Desmo SDK
    participant Backend as Desmo Backend
    participant Webhook as Your Webhook

    App->>SDK: Start Session
    SDK->>Backend: POST /v1/sessions/start
    Backend->>Webhook: session.started
    
    SDK->>Backend: Stream Telemetry
    Backend->>Backend: Process Data
    Backend->>Webhook: insight.updated
    
    App->>SDK: Stop Session
    SDK->>Backend: POST /v1/sessions/stop
    Backend->>Webhook: session.stopped
```

## Setting Up Webhooks

You can configure webhooks via the Dashboard UI or programmatically via API.

### Option 1: Dashboard

1. Go to [Desmo Dashboard](https://app.getdesmo.io) → **Settings**
2. Enter your **Webhook URL** (must be HTTPS)
3. Copy and securely store the generated **secret**

### Option 2: API (Recommended for Automation)

Configure webhooks programmatically using your secret key (`sk_`):

```bash theme={null}
# Create webhook
curl -X POST https://api.getdesmo.io/v1/webhooks \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://api.yourcompany.com/webhooks/desmo"}'
```

**Response:**

```json theme={null}
{
  "id": "64a1b2c3d4e5f6789",
  "url": "https://api.yourcompany.com/webhooks/desmo",
  "enabled": true,
  "createdAt": "2026-01-12T10:00:00Z",
  "secret": "whsec_abc123..."
}
```

<Warning>
  The `secret` is only returned once during creation. Store it securely—you'll use it to verify webhook signatures.
</Warning>

### API Endpoints

| Method   | Endpoint              | Description                       |
| -------- | --------------------- | --------------------------------- |
| `POST`   | `/v1/webhooks`        | Create webhook configuration      |
| `GET`    | `/v1/webhooks`        | Get current webhook configuration |
| `PUT`    | `/v1/webhooks`        | Update webhook (URL, enabled)     |
| `DELETE` | `/v1/webhooks`        | Delete webhook configuration      |
| `POST`   | `/v1/webhooks/rotate` | Rotate webhook secret             |

**Update webhook:**

```bash theme={null}
curl -X PUT https://api.getdesmo.io/v1/webhooks \
  -H "Authorization: Bearer sk_live_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://new-url.com/webhook", "enabled": true}'
```

**Rotate secret:**

```bash theme={null}
curl -X POST https://api.getdesmo.io/v1/webhooks/rotate \
  -H "Authorization: Bearer sk_live_your_secret_key"
```

## Webhook Payloads

### session.started

Sent when a driver starts a new delivery session.

```json theme={null}
{
  "event": "session.started",
  "timestamp": "2026-01-12T10:30:00Z",
  "data": {
    "sessionId": "sess_abc123",
    "deliveryId": "DEL-001",
    "organizationId": "org_xyz",
    "externalRiderId": "driver_456",
    "sessionType": "drop",
    "status": "recording",
    "env": "live",
    "address": {
      "line1": "123 Main St",
      "city": "Bangalore",
      "country": "IN",
      "lat": 12.9716,
      "lng": 77.5946
    },
    "createdAt": "2026-01-12T10:30:00Z",
    "endedAt": null
  }
}
```

### session.stopped

Sent when a driver ends a delivery session.

```json theme={null}
{
  "event": "session.stopped",
  "timestamp": "2026-01-12T10:45:00Z",
  "data": {
    "sessionId": "sess_abc123",
    "deliveryId": "DEL-001",
    "organizationId": "org_xyz",
    "externalRiderId": "driver_456",
    "sessionType": "drop",
    "status": "completed",
    "env": "live",
    "address": {
      "line1": "123 Main St",
      "city": "Bangalore",
      "country": "IN",
      "lat": 12.9716,
      "lng": 77.5946
    },
    "createdAt": "2026-01-12T10:30:00Z",
    "endedAt": "2026-01-12T10:45:00Z"
  }
}
```

### insight.updated

Sent when Desmo generates or updates insights for a session.

```json theme={null}
{
  "event": "insight.updated",
  "timestamp": "2026-01-12T10:46:00Z",
  "data": {
    "sessionId": "sess_abc123",
    "deliveryId": "DEL-001",
    "organizationId": "org_xyz",
    "status": "ready",
    "summary": {
      "rightDoorstep": true,
      "suspiciousBehavior": false,
      "floorsClimbed": 3,
      "elevatorUsed": true,
      "frictionScore": 0.15,
      "fraudScore": 0.05,
      "visibilityScore": 0.92
    },
    "reasonCodes": ["FULL_ATTEMPT", "DELIVERED_TO_RIGHT_DOORSTEP"],
    "events": [
      {
        "type": "WALKING",
        "tStart": 0,
        "tEnd": 45,
        "confidence": 0.95
      },
      {
        "type": "ELEVATOR",
        "tStart": 50,
        "tEnd": 65,
        "metadata": { "floors": 3, "direction": "up" }
      },
      {
        "type": "GATE",
        "tStart": 70,
        "confidence": 0.88
      }
    ]
  }
}
```

### Reason Codes

| Code                          | Description                               |
| ----------------------------- | ----------------------------------------- |
| `FULL_ATTEMPT`                | Complete delivery attempt detected        |
| `DELIVERED_TO_RIGHT_DOORSTEP` | GPS and movement confirm correct location |
| `PARTIAL_ATTEMPT`             | Incomplete delivery attempt               |
| `POSSIBLE_LOBBY_HANDOFF`      | Delivery may have been left in lobby      |
| `POSSIBLE_NO_ATTEMPT`         | No delivery attempt detected              |
| `INSUFFICIENT_DATA`           | Not enough telemetry to verify            |

## Verifying Webhook Signatures

All webhook requests include a signature header to verify authenticity:

```
X-Desmo-Signature: sha256=abc123...
X-Desmo-Event: session.started
X-Desmo-Delivery: sess_abc123
```

### Verification Example (Node.js)

```javascript theme={null}
const crypto = require('crypto');

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

// In your webhook handler
app.post('/webhook/desmo', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-desmo-signature'];
  const payload = req.body.toString();
  
  if (!verifyWebhookSignature(payload, signature, process.env.DESMO_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  const event = JSON.parse(payload);
  
  switch (event.event) {
    case 'session.started':
      console.log(`Delivery started: ${event.data.sessionId}`);
      // Track in your system
      break;
    case 'session.stopped':
      console.log(`Delivery ended: ${event.data.sessionId}`);
      // Mark delivery complete
      break;
    case 'insight.updated':
      console.log(`Insights ready: ${event.data.reasonCodes.join(', ')}`);
      // Process fraud scores, verification data
      break;
  }
  
  res.status(200).send('OK');
});
```

### Verification Example (Python)

```python theme={null}
import hmac
import hashlib
import json

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    
    expected_signature = f"sha256={expected}"
    return hmac.compare_digest(signature, expected_signature)

# In your webhook handler (FastAPI)
@app.post("/webhook/desmo")
async def handle_webhook(request: Request):
    signature = request.headers.get("X-Desmo-Signature")
    payload = await request.body()
    
    if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")
    
    event = json.loads(payload)
    
    if event["event"] == "session.started":
        print(f"Delivery started: {event['data']['sessionId']}")
    elif event["event"] == "session.stopped":
        print(f"Delivery ended: {event['data']['sessionId']}")
    elif event["event"] == "insight.updated":
        print(f"Insights: {event['data']['reasonCodes']}")
    
    return {"status": "ok"}
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Respond Quickly" icon="bolt">
    Return a `200 OK` response within 5 seconds. Process webhook data asynchronously if needed.
  </Card>

  <Card title="Handle Duplicates" icon="copy">
    Webhooks may be retried. Use `sessionId` to deduplicate events idempotently.
  </Card>

  <Card title="Verify Signatures" icon="shield-check">
    Always verify the `X-Desmo-Signature` header before processing webhook data.
  </Card>

  <Card title="Use HTTPS" icon="lock">
    Your webhook endpoint must use HTTPS with a valid SSL certificate.
  </Card>
</CardGroup>

## Retry Policy

If your webhook endpoint returns a non-2xx status code or times out, Desmo will retry the delivery:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 1 minute   |
| 2nd retry | 5 minutes  |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours    |
| 5th retry | 24 hours   |

After 5 failed attempts, the webhook delivery is marked as failed. You can view failed deliveries in the Dashboard.

## Testing Webhooks

### Using the Dashboard

1. Go to **Settings → Webhooks**
2. Click **Send Test Event**
3. A sample event will be sent to your configured URL

### Using ngrok for Local Development

```bash theme={null}
# Expose your local server
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhook/desmo
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Webhook not receiving events">
    * Verify your webhook URL is correct and publicly accessible
    * Check that your server responds with `200 OK`
    * Ensure your firewall allows incoming HTTPS traffic
    * Check the webhook logs in the Dashboard for error details
  </Accordion>

  <Accordion title="Signature verification failing">
    * Ensure you're using the raw request body (not parsed JSON)
    * Verify you're using the correct webhook secret
    * Check that no middleware is modifying the request body
  </Accordion>

  <Accordion title="Duplicate events">
    * This is expected behavior for reliability
    * Use `sessionId` as an idempotency key
    * Store processed session IDs to skip duplicates
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Explore the full API for fetching sessions and insights
  </Card>

  <Card title="SDK Integration" icon="mobile" href="/essentials/android-quickstart">
    Set up the mobile SDK to start collecting telemetry
  </Card>
</CardGroup>
