Skip to main content

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.
Webhooks are the recommended way to integrate Desmo into your backend systems. They provide real-time notifications for every delivery across your entire fleet.

Events

Desmo sends three types of webhook events:
EventWhen it firesUse case
session.startedDriver starts a delivery sessionTrack active deliveries in your system
session.stoppedDriver ends a delivery sessionMark delivery as complete
insight.updatedProcessing generates/updates insightsGet fraud scores, verification data

How It Works

Setting Up Webhooks

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

Option 1: Dashboard

  1. Go to Desmo DashboardSettings
  2. Enter your Webhook URL (must be HTTPS)
  3. Copy and securely store the generated secret
Configure webhooks programmatically using your secret key (sk_):
# 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:
{
  "id": "64a1b2c3d4e5f6789",
  "url": "https://api.yourcompany.com/webhooks/desmo",
  "enabled": true,
  "createdAt": "2026-01-12T10:00:00Z",
  "secret": "whsec_abc123..."
}
The secret is only returned once during creation. Store it securely—you’ll use it to verify webhook signatures.

API Endpoints

MethodEndpointDescription
POST/v1/webhooksCreate webhook configuration
GET/v1/webhooksGet current webhook configuration
PUT/v1/webhooksUpdate webhook (URL, enabled)
DELETE/v1/webhooksDelete webhook configuration
POST/v1/webhooks/rotateRotate webhook secret
Update webhook:
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:
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.
{
  "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.
{
  "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.
{
  "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

CodeDescription
FULL_ATTEMPTComplete delivery attempt detected
DELIVERED_TO_RIGHT_DOORSTEPGPS and movement confirm correct location
PARTIAL_ATTEMPTIncomplete delivery attempt
POSSIBLE_LOBBY_HANDOFFDelivery may have been left in lobby
POSSIBLE_NO_ATTEMPTNo delivery attempt detected
INSUFFICIENT_DATANot 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)

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)

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

Respond Quickly

Return a 200 OK response within 5 seconds. Process webhook data asynchronously if needed.

Handle Duplicates

Webhooks may be retried. Use sessionId to deduplicate events idempotently.

Verify Signatures

Always verify the X-Desmo-Signature header before processing webhook data.

Use HTTPS

Your webhook endpoint must use HTTPS with a valid SSL certificate.

Retry Policy

If your webhook endpoint returns a non-2xx status code or times out, Desmo will retry the delivery:
AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry24 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

# Expose your local server
ngrok http 3000

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

Troubleshooting

  • 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
  • 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
  • This is expected behavior for reliability
  • Use sessionId as an idempotency key
  • Store processed session IDs to skip duplicates

Next Steps