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:
Event When it fires Use case session.startedDriver starts a delivery session Track active deliveries in your system session.stoppedDriver ends a delivery session Mark delivery as complete insight.updatedProcessing generates/updates insights Get 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
Go to Desmo Dashboard → Settings
Enter your Webhook URL (must be HTTPS)
Copy and securely store the generated secret
Option 2: API (Recommended for Automation)
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
Method Endpoint Description 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
Code Description 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:
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
Go to Settings → Webhooks
Click Send Test Event
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
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
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
This is expected behavior for reliability
Use sessionId as an idempotency key
Store processed session IDs to skip duplicates
Next Steps