Webhooks

Register HTTPS endpoints to receive real-time event notifications from SALLY. SALLY sends HTTP POST requests with HMAC-signed payloads whenever relevant events occur.

Webhook management has moved to Console.

Manage Webhooks

Supported Events

All events use the sally.* prefix:

EventWhen it fires
sally.load.createdA new load is added to the system
sally.load.updatedA load's status, stops, or assignment changes
sally.route.plannedA route plan is created for a driver
sally.alert.firedAn alert is generated (HOS violation, delay, deviation)

Subscribe to * to receive all current and future events.

Payload Format

Every delivery has a standard envelope:

{
  "id": "evt_a1b2c3d4",
  "event": "sally.alert.fired",
  "timestamp": "2026-02-23T14:00:00Z",
  "webhookId": "wh_x1y2z3",
  "data": { ... }
}

sally.alert.fired

{
  "id": "evt_h8i9j0k1",
  "event": "sally.alert.fired",
  "timestamp": "2026-02-23T14:00:00Z",
  "webhookId": "wh_a1b2c3d4",
  "data": {
    "id": "alt_x1y2z3w4",
    "type": "HOS_APPROACHING_DRIVE_LIMIT",
    "priority": "HIGH",
    "status": "ACTIVE",
    "title": "Driver approaching 11-hour drive limit",
    "message": "Mike Johnson has 45 minutes of drive time remaining.",
    "driverId": "drv_a1b2c3d4",
    "routePlanId": "rte_f8e7d6c5",
    "createdAt": "2026-02-23T14:00:00Z"
  }
}

sally.route.planned

{
  "id": "evt_d4e5f6g7",
  "event": "sally.route.planned",
  "timestamp": "2026-02-23T08:00:00Z",
  "webhookId": "wh_a1b2c3d4",
  "data": {
    "id": "rte_f8e7d6c5",
    "status": "PLANNED",
    "driverId": "drv_a1b2c3d4",
    "vehicleId": "veh_t4r5k6",
    "hosCompliant": true,
    "estimatedDurationHours": 9.5,
    "estimatedDistanceMiles": 487,
    "createdAt": "2026-02-23T08:00:00Z"
  }
}

sally.load.created / sally.load.updated

{
  "id": "evt_a1b2c3d4",
  "event": "sally.load.created",
  "timestamp": "2026-02-23T16:15:00Z",
  "webhookId": "wh_a1b2c3d4",
  "data": {
    "id": "load_l1l2l3",
    "referenceNumber": "ORD-2026-0042",
    "status": "PENDING",
    "tenantId": "tnt_acme001",
    "createdAt": "2026-02-23T16:15:00Z"
  }
}

Signature Verification

SALLY signs every request with HMAC-SHA256. Always verify signatures before processing.

import crypto from 'crypto'

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

// Express.js
app.post('/webhooks/sally', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-sally-signature']
  const ts = req.headers['x-sally-timestamp']

  if (!verifyWebhook(req.body.toString(), sig, ts, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature')
  }

  // Respond immediately, process async
  res.status(200).send('OK')
  setImmediate(() => handleEvent(JSON.parse(req.body)))
})
import hmac, hashlib

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

Retry Policy

AttemptDelay
1st retry30 seconds
2nd retry2 minutes
3rd retry10 minutes
4th retry30 minutes
5th retry1 hour

After 5 failures the delivery is marked failed. Use the delivery logs in the manager above to retry manually.

Local Testing

ngrok: Expose localhost to the internet for testing.

ngrok http 3000
# Use the https://xxx.ngrok.io URL as your webhook endpoint

webhook.site: For quick payload inspection without a local server — visit webhook.site.

Troubleshooting

Events not arriving:

  • Verify endpoint is active in the manager above
  • Your URL must be public HTTPS (not localhost)
  • Check delivery logs for error details

Signature verification failing:

  • Use the raw request body — do not parse JSON before computing HMAC
  • Trim whitespace from your secret
  • Must use HMAC-SHA256 (not SHA1 or MD5)

Timeout errors: Respond 200 OK immediately, process the event asynchronously.

Full Reference

See the Webhook Integration Guide for complete handler examples, idempotency patterns, and best practices.