N-Ways
Developer docsWebhooks

Webhooks

Events sent to your server as they happen: a rider near the door, a delivery done, a vehicle that hasn’t moved.

On this page
Note:

Webhooks come with fleet tracking. Webhooks reference

Events

EventWhendata
task.createdA delivery was createdtask
task.assignedA vehicle was given the delivery (or another one took it over)task
task.arrivingThe rider is within the task’s arrivingS (3 minutes by default) or 250 m of the drop-offtask
task.completedMarked deliveredtask
task.failedMarked failedtask
vehicle.offlineA vehicle sent nothing for offlineAfterS (10 minutes by default), once per silencevehicle
task.updatedYou edited the delivery (PATCH /tasks/{id}); changed lists the fieldstask, changed
task.startedAn N-Ways driver started the delivery (driver connect)task
delivery.ratedThe customer rated the delivery (1–5 stars, seen only by your company)task, rating
zone.entered, zone.exitedA vehicle entered or left one of your zonesvehicle, zone
connection.approved, connection.declinedAn N-Ways driver approved or declined your invitationconnection or invite
connection.revoked, connection.endedThe driver stopped sharing with you, or your company ended the connectionconnection
driver.availability_changedA connected driver switched Available for you on or offconnection
dispatch.offered, .accepted, .declined, .expired, .withdrawnA delivery request to an N-Ways driver, and what happened to itoffer, task

ERP teams (SAP, Odoo, Microsoft Dynamics): see Connect your ERP for field mapping, signature checks in C#, Groovy and Python, and reconciling with the export API.

Alerts (an area left, a zone entered, a vehicle silent or stopped) come as asset.* events too, and can go to email, push, Google Sheets, Telegram or Excel: see Alerts and forwarding.

Add an endpoint

Give an HTTPS address on your server, and the events it wants (none listed means all). The answer has the endpoint’s signing secret: it’s shown only now, so store it.

cURL
curl -X POST "https://nways.ng/maps/v1/fleet/webhooks" \
  -H "X-NWays-Api-Key: YOUR_SERVER_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/nways/events", "events": ["task.arriving", "task.completed", "task.failed"]}'

POST /webhooks/{id}/test sends a ping event to check it works; GET /webhooks/{id}/deliveries shows what was sent, your server’s answers and the next retry.

What arrives, and checking it’s from us

Each event is a POST with a JSON body (id, type, createdAt, data) and these headers:

HeaderValue
X-NWays-EventThe event type
X-NWays-DeliveryThis delivery’s ID (dlv_…), the same on every retry
X-NWays-Signaturet=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of "<t>.<raw body>" with your endpoint’s secret

Check the signature on the raw body, before parsing it, and refuse a timestamp more than 5 minutes off:

Node.js
import { createHmac, timingSafeEqual } from 'node:crypto'
import { createServer } from 'node:http'

const SECRET = process.env.NWAYS_WEBHOOK_SECRET

function verified(raw, header) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false
  const want = createHmac('sha256', SECRET).update(`${parts.t}.${raw}`).digest('hex')
  return want.length === parts.v1?.length && timingSafeEqual(Buffer.from(want), Buffer.from(parts.v1))
}

createServer((req, res) => {
  let raw = ''
  req.on('data', (c) => (raw += c))
  req.on('end', () => {
    if (!verified(raw, req.headers['x-nways-signature'] ?? '')) return res.writeHead(401).end()
    const event = JSON.parse(raw)
    if (event.type === 'task.arriving') console.log(`Order ${event.data.task.id}: rider arriving`)
    res.writeHead(204).end() // answer fast; do slow work afterwards
  })
}).listen(8080)
Python
import hashlib
import hmac
import os
import time

SECRET = os.environ["NWAYS_WEBHOOK_SECRET"].encode()


def verified(raw: bytes, header: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    if abs(time.time() - int(parts.get("t", 0))) > 300:
        return False
    want = hmac.new(SECRET, parts["t"].encode() + b"." + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, parts.get("v1", ""))

Retries and order

  • Answer with any 2xx within 10 seconds. Anything else is tried again after 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1, 3, 6 and 12 hours, then marked failed.
  • Events can arrive more than once or out of order: keep the event ids you’ve handled, and trust the task’s status over the order events came in.
  • Redirects aren’t followed, and the address must be public.
  • Leaked a secret? POST /webhooks/{id}/rotate-secret gives a new one at once.