Webhooks
Endpoints on your server that N-Ways calls when a delivery or a vehicle changes.
On this page
- List webhook endpoints
- Add an endpoint (up to 10); the answer has its signing secret, shown once
- Change an endpoint's url, events or active
- Delete an endpoint and its delivery log
- A new signing secret (shown once); the old one stops at once
- Queue a ping event to the endpoint
- The endpoint's delivery log, newest first (kept 30 days)
- Send a delivery again now (failed, or delivered and wanted again); its attempts are kept
- An event, POSTed to your endpoint
Send your key as key=YOUR_API_KEY on the address, or in the X-NWays-Api-Key header. API keys
Base address: https://nways.ng/maps/v1/fleet
List webhook endpoints
GET https://nways.ng/maps/v1/fleet/webhooks
curl "https://nways.ng/maps/v1/fleet/webhooks" \
-H "X-NWays-Api-Key: YOUR_API_KEY"const res = await fetch('https://nways.ng/maps/v1/fleet/webhooks', {
headers: { 'X-NWays-Api-Key': process.env.NWAYS_API_KEY },
})
const data = await res.json()import os
import requests
res = requests.get(
"https://nways.ng/maps/v1/fleet/webhooks",
headers={"X-NWays-Api-Key": os.environ["NWAYS_API_KEY"]},
timeout=10,
)
data = res.json()Response
Endpoints and the events there are
| Field | Type | Description |
|---|---|---|
webhooks | Webhook[] | Required. |
webhooks[].id | string | Required. |
webhooks[].url | string (uri) | Required. |
webhooks[].events | string[] | Required. |
webhooks[].active | boolean | Required. |
webhooks[].secret | string | Only when made or rotated. |
webhooks[].headers | string[] | Names of the custom headers sent with each delivery (values are never shown). |
webhooks[].createdAt | string (date-time) | Required. |
webhooks[].updatedAt | string (date-time) | Required. |
events | string[] | Required. |
{
"webhooks": [
{
"id": "wh_Ub3yH9sLk2",
"url": "string",
"events": [
"task.created"
],
"active": true,
"secret": "whsec_…",
"headers": [
"string"
],
"createdAt": "2026-10-15T09:00:00+01:00",
"updatedAt": "2026-10-15T09:00:00+01:00"
}
],
"events": [
"task.created"
]
}Errors
defaultError
Add an endpoint (up to 10); the answer has its signing secret, shown once
POST https://nways.ng/maps/v1/fleet/webhooks
Request body
| Field | Type | Description |
|---|---|---|
url | string (uri) | Required. https, a public address; redirects are not followed. |
events | string[] | Empty = every event. |
headers | object | Sent with every delivery (an auth token your CRM asks for). Not X-NWays-*, Host, Content-Type, User-Agent. |
curl -X POST "https://nways.ng/maps/v1/fleet/webhooks" \
-H "X-NWays-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"string","events":["task.created"],"headers":{}}'const res = await fetch('https://nways.ng/maps/v1/fleet/webhooks', {
method: 'POST',
headers: { 'X-NWays-Api-Key': process.env.NWAYS_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
"url": "string",
"events": [
"task.created"
],
"headers": {}
}),
})
const data = await res.json()import os
import requests
res = requests.post(
"https://nways.ng/maps/v1/fleet/webhooks",
headers={"X-NWays-Api-Key": os.environ["NWAYS_API_KEY"]},
json={"url": "string", "events": ["task.created"], "headers": {}},
timeout=10,
)
data = res.json()Response
Created
| Field | Type | Description |
|---|---|---|
id | string | Required. |
url | string (uri) | Required. |
events | string[] | Required. |
active | boolean | Required. |
secret | string | Only when made or rotated. |
headers | string[] | Names of the custom headers sent with each delivery (values are never shown). |
createdAt | string (date-time) | Required. |
updatedAt | string (date-time) | Required. |
{
"id": "wh_Ub3yH9sLk2",
"url": "string",
"events": [
"task.created"
],
"active": true,
"secret": "whsec_…",
"headers": [
"string"
],
"createdAt": "2026-10-15T09:00:00+01:00",
"updatedAt": "2026-10-15T09:00:00+01:00"
}Errors
defaultError
Change an endpoint's url, events or active
PATCH https://nways.ng/maps/v1/fleet/webhooks/{id}
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | Required. |
Request body
| Field | Type | Description |
|---|---|---|
url | string (uri) | |
events | string[] | |
active | boolean | Off: pending deliveries fail and no new ones are queued. |
curl -X PATCH "https://nways.ng/maps/v1/fleet/webhooks/string" \
-H "X-NWays-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"string","events":["task.created"],"active":true}'const res = await fetch('https://nways.ng/maps/v1/fleet/webhooks/string', {
method: 'PATCH',
headers: { 'X-NWays-Api-Key': process.env.NWAYS_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
"url": "string",
"events": [
"task.created"
],
"active": true
}),
})
const data = await res.json()import os
import requests
res = requests.patch(
"https://nways.ng/maps/v1/fleet/webhooks/string",
headers={"X-NWays-Api-Key": os.environ["NWAYS_API_KEY"]},
json={"url": "string", "events": ["task.created"], "active": True},
timeout=10,
)
data = res.json()Response
Endpoint
| Field | Type | Description |
|---|---|---|
id | string | Required. |
url | string (uri) | Required. |
events | string[] | Required. |
active | boolean | Required. |
secret | string | Only when made or rotated. |
headers | string[] | Names of the custom headers sent with each delivery (values are never shown). |
createdAt | string (date-time) | Required. |
updatedAt | string (date-time) | Required. |
{
"id": "wh_Ub3yH9sLk2",
"url": "string",
"events": [
"task.created"
],
"active": true,
"secret": "whsec_…",
"headers": [
"string"
],
"createdAt": "2026-10-15T09:00:00+01:00",
"updatedAt": "2026-10-15T09:00:00+01:00"
}Errors
defaultError
Delete an endpoint and its delivery log
DELETE https://nways.ng/maps/v1/fleet/webhooks/{id}
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | Required. |
curl -X DELETE "https://nways.ng/maps/v1/fleet/webhooks/string" \
-H "X-NWays-Api-Key: YOUR_API_KEY"const res = await fetch('https://nways.ng/maps/v1/fleet/webhooks/string', {
method: 'DELETE',
headers: { 'X-NWays-Api-Key': process.env.NWAYS_API_KEY },
})
const data = await res.json()import os
import requests
res = requests.delete(
"https://nways.ng/maps/v1/fleet/webhooks/string",
headers={"X-NWays-Api-Key": os.environ["NWAYS_API_KEY"]},
timeout=10,
)
data = res.json()Response
Deleted
No content.
Errors
defaultError
A new signing secret (shown once); the old one stops at once
POST https://nways.ng/maps/v1/fleet/webhooks/{id}/rotate-secret
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | Required. |
curl -X POST "https://nways.ng/maps/v1/fleet/webhooks/string/rotate-secret" \
-H "X-NWays-Api-Key: YOUR_API_KEY"const res = await fetch('https://nways.ng/maps/v1/fleet/webhooks/string/rotate-secret', {
method: 'POST',
headers: { 'X-NWays-Api-Key': process.env.NWAYS_API_KEY },
})
const data = await res.json()import os
import requests
res = requests.post(
"https://nways.ng/maps/v1/fleet/webhooks/string/rotate-secret",
headers={"X-NWays-Api-Key": os.environ["NWAYS_API_KEY"]},
timeout=10,
)
data = res.json()Response
Endpoint with secret
| Field | Type | Description |
|---|---|---|
id | string | Required. |
url | string (uri) | Required. |
events | string[] | Required. |
active | boolean | Required. |
secret | string | Only when made or rotated. |
headers | string[] | Names of the custom headers sent with each delivery (values are never shown). |
createdAt | string (date-time) | Required. |
updatedAt | string (date-time) | Required. |
{
"id": "wh_Ub3yH9sLk2",
"url": "string",
"events": [
"task.created"
],
"active": true,
"secret": "whsec_…",
"headers": [
"string"
],
"createdAt": "2026-10-15T09:00:00+01:00",
"updatedAt": "2026-10-15T09:00:00+01:00"
}Errors
defaultError
Queue a ping event to the endpoint
POST https://nways.ng/maps/v1/fleet/webhooks/{id}/test
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | Required. |
curl -X POST "https://nways.ng/maps/v1/fleet/webhooks/string/test" \
-H "X-NWays-Api-Key: YOUR_API_KEY"const res = await fetch('https://nways.ng/maps/v1/fleet/webhooks/string/test', {
method: 'POST',
headers: { 'X-NWays-Api-Key': process.env.NWAYS_API_KEY },
})
const data = await res.json()import os
import requests
res = requests.post(
"https://nways.ng/maps/v1/fleet/webhooks/string/test",
headers={"X-NWays-Api-Key": os.environ["NWAYS_API_KEY"]},
timeout=10,
)
data = res.json()Response
Queued
| Field | Type | Description |
|---|---|---|
deliveryId | string | Required. |
{
"deliveryId": "dlv_Qa71ZcR0mX"
}Errors
defaultError
The endpoint's delivery log, newest first (kept 30 days)
GET https://nways.ng/maps/v1/fleet/webhooks/{id}/deliveries
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | Required. |
status | query | string | One of: pending, delivered, failed. |
limit | query | integer | Default: 50. ≥ 1, ≤ 200. |
curl "https://nways.ng/maps/v1/fleet/webhooks/string/deliveries" \
-H "X-NWays-Api-Key: YOUR_API_KEY"const res = await fetch('https://nways.ng/maps/v1/fleet/webhooks/string/deliveries', {
headers: { 'X-NWays-Api-Key': process.env.NWAYS_API_KEY },
})
const data = await res.json()import os
import requests
res = requests.get(
"https://nways.ng/maps/v1/fleet/webhooks/string/deliveries",
headers={"X-NWays-Api-Key": os.environ["NWAYS_API_KEY"]},
timeout=10,
)
data = res.json()Response
Deliveries
| Field | Type | Description |
|---|---|---|
deliveries | Delivery[] | Required. |
deliveries[].id | string | Required. |
deliveries[].event | string | Required. |
deliveries[].status | string | Required. One of: pending, delivered, failed. |
deliveries[].attempts | integer | Required. |
deliveries[].lastStatus | integer | Required. Your endpoint's last HTTP status (0 = no answer). |
deliveries[].lastError | string | Required. |
deliveries[].nextAttemptAt | string (date-time) | null | Required. |
deliveries[].createdAt | string (date-time) | Required. |
deliveries[].deliveredAt | string (date-time) | null | Required. |
deliveries[].payload | Event | Required. |
deliveries[].payload.id | string | Required. |
deliveries[].payload.type | string | Required. An EventType, or ping from the test call. |
deliveries[].payload.version | string | Required. The payload shape's version: fields are only added within a version. |
deliveries[].payload.createdAt | string (date-time) | Required. |
deliveries[].payload.data | object | Required. |
deliveries[].payload.data.task | Task | |
deliveries[].payload.data.vehicle | any | A Vehicle; {id, name} in zone events. |
deliveries[].payload.data.zone | object | |
deliveries[].payload.data.at | string (date-time) | |
deliveries[].payload.data.position | object | |
deliveries[].payload.data.alert | AlertEvent | |
deliveries[].payload.data.changed | string[] | task.updated: the fields the edit changed. |
{
"deliveries": [
{
"id": "dlv_Qa71ZcR0mX",
"event": "string",
"status": "pending",
"attempts": 0,
"lastStatus": 0,
"lastError": "string",
"nextAttemptAt": "2026-10-15T09:00:00+01:00",
"createdAt": "2026-10-15T09:00:00+01:00",
"deliveredAt": "2026-10-15T09:00:00+01:00",
"payload": {
"id": "evt_9bQ2xLm0aZc4RwYeT7Kp",
"type": "string",
"version": "2026-10-16",
"createdAt": "2026-10-15T09:00:00+01:00",
"data": {
"task": {},
"vehicle": "string",
"zone": {},
"at": "2026-10-15T09:00:00+01:00",
"position": {},
"alert": {},
"changed": []
}
}
}
]
}Errors
defaultError
Send a delivery again now (failed, or delivered and wanted again); its attempts are kept
POST https://nways.ng/maps/v1/fleet/webhooks/{id}/deliveries/{deliveryId}/retry
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id | path | string | Required. |
deliveryId | path | string | Required. |
curl -X POST "https://nways.ng/maps/v1/fleet/webhooks/string/deliveries/string/retry" \
-H "X-NWays-Api-Key: YOUR_API_KEY"const res = await fetch('https://nways.ng/maps/v1/fleet/webhooks/string/deliveries/string/retry', {
method: 'POST',
headers: { 'X-NWays-Api-Key': process.env.NWAYS_API_KEY },
})
const data = await res.json()import os
import requests
res = requests.post(
"https://nways.ng/maps/v1/fleet/webhooks/string/deliveries/string/retry",
headers={"X-NWays-Api-Key": os.environ["NWAYS_API_KEY"]},
timeout=10,
)
data = res.json()Response
Queued
| Field | Type | Description |
|---|---|---|
id | string | Required. |
status | any | Required. |
{
"id": "string",
"status": "string"
}Errors
defaultError
An event, POSTed to your endpoint
POST your endpoint’s URL
Headers: X-NWays-Event (the type), X-NWays-Delivery (dlv_…, the same on retries), X-NWays-Signature: t=<unix seconds>,v1=<hex> where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). Check it with your endpoint's secret and refuse timestamps more than 5 minutes off. Answer any 2xx within 10 s; anything else is retried after 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h and 12 h, then marked failed. Events may arrive out of order or more than once: use the event id and the task's status. Events: task.created, task.assigned, task.arriving, task.completed, task.failed (data.task, a Task), vehicle.offline (data.vehicle, a Vehicle; once per silence longer than offlineAfterS), and zone.entered / zone.exited (data.zone {id, name}, data.vehicle {id, name}, data.at, data.position {lat, lng}).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
X-NWays-Signature | header | string | Required. |
X-NWays-Event | header | string | Required. One of: task.created, task.assigned, task.arriving, task.completed, task.failed, vehicle.offline, zone.entered, zone.exited, asset.geofence_exit, asset.geofence_enter, asset.silent, asset.idle, asset.alert_resolved, connection.approved, connection.declined, connection.revoked, connection.ended, driver.availability_changed, dispatch.offered, dispatch.accepted, dispatch.declined, dispatch.expired, dispatch.withdrawn, task.started, delivery.rated, fleet.crash.detected, fleet.crash.resolved, task.updated. |
X-NWays-Delivery | header | string | Required. |
Request body
| Field | Type | Description |
|---|---|---|
id | string | Required. |
type | string | Required. An EventType, or ping from the test call. |
version | string | Required. The payload shape's version: fields are only added within a version. |
createdAt | string (date-time) | Required. |
data | object | Required. |
data.task | Task | |
data.task.id | string | Required. |
data.task.ref | string | Required. The delivery's short number in the project, given by N-Ways ("1042", shown #1042): for people, in alerts and lists. |
data.task.status | string | Required. One of: created, assigned, picked_up, arriving, completed, failed, cancelled. |
data.task.vehicleId | string | null | Required. |
data.task.pickup | Stop | null | Required. |
data.task.dropoff | Stop | Required. |
data.task.dropoff.lat | number | Required. |
data.task.dropoff.lng | number | Required. |
data.task.dropoff.name | string | Up to 120 characters. |
data.task.dropoff.address | string | Up to 300 characters. |
data.task.recipientName | string | Required. |
data.task.recipientPhone | string | |
data.task.notes | string | Required. |
data.task.lang | string | Required. One of: en, fr, pt. |
data.task.arrivingS | integer | Required. |
data.task.eta | ETA | null | Required. Null until a fresh position is in, and once the task ends. |
data.task.dueAt | string (date-time) | null | Required. |
data.task.late | boolean | Required. Open and past dueAt, or expected after it. |
data.task.trackingUrl | string (uri) | null | Required. Null once expired. |
data.task.trackingExpiresAt | string (date-time) | null | Required. |
data.task.failureReason | string | |
data.task.createdAt | string (date-time) | Required. |
data.task.assignedAt | string (date-time) | null | Required. |
data.task.pickedUpAt | string (date-time) | null | Required. |
data.task.arrivingAt | string (date-time) | null | Required. |
data.task.endedAt | string (date-time) | null | Required. |
data.task.area | Area | null | The delivery's own area: leaving it raises asset.geofence_exit. |
data.task.proofs | Proof[] | Only in GET /tasks/{id}: the rider's photos and signatures (N-Ways Business). |
data.task.proofs[].id | string | Required. |
data.task.proofs[].kind | string | Required. One of: photo, signature. |
data.task.proofs[].contentType | string | Required. One of: image/jpeg, image/png, image/webp. |
data.task.proofs[].bytes | integer | Required. |
data.task.proofs[].lat | number | null | Required. |
data.task.proofs[].lng | number | null | Required. |
data.task.proofs[].by | string | Required. The rider's name. |
data.task.proofs[].createdAt | string (date-time) | Required. |
data.task.endNote | string | Only in GET /tasks/{id}: what the rider wrote when finishing. |
data.task.recipientEmail | string | |
data.task.proofRequired | string[] | The delivery's own, else your settings'. |
data.task.proofRequiredOverride | string[] | null | |
data.task.packageSize | string | null | One of: small, medium, large, null. |
data.task.itemDescription | string | |
data.task.reference | string | |
data.task.scheduledAt | string (date-time) | null | |
data.task.connectionId | string | null | The N-Ways driver's connection that accepted it. |
data.task.acceptedAt | string (date-time) | null | |
data.task.startedAt | string (date-time) | null | |
data.task.editable | string | What PATCH /tasks/{id} may change now: everything, only notes and dueAt (picked up or started), or nothing (ended). One of: all, limited, none. |
data.task.editedAt | string (date-time) | null | The last edit. |
data.task.editedFields | string[] | What the last edit changed. |
data.task.pinDelivery | PinDelivery | How the delivery PIN is reaching the customer, when the delivery asks for one and PINs go on WhatsApp. Never the PIN itself. |
data.task.pinDelivery.channel | string | Required. One of: whatsapp, email, none. |
data.task.pinDelivery.state | string | Required. One of: sending, sent, delivered, read, failed. |
data.task.pinDelivery.whatsappErrorCode | integer | null | Required. WhatsApp's failure (131026: not on WhatsApp), kept after the PIN went by email. |
data.task.pinDelivery.needsEmail | boolean | Required. The customer hasn't got the PIN: send it with POST /tasks/{id}/pin-email. |
data.task.pinDelivery.updatedAt | string (date-time) | Required. |
data.vehicle | any | A Vehicle; {id, name} in zone events. |
data.zone | object | |
data.zone.id | string | |
data.zone.name | string | |
data.at | string (date-time) | |
data.position | object | |
data.position.lat | number | |
data.position.lng | number | |
data.alert | AlertEvent | |
data.alert.id | string | Required. |
data.alert.ruleId | string | Required. A rule id, or default:dispatch for a delivery area with no rule of its own, default:crash for a crash with no crash rule. |
data.alert.ruleName | string | |
data.alert.kind | string | Required. One of: geofence_exit, geofence_enter, silent, idle, crash, tracker_alarm. |
data.alert.vehicleId | string | Required. |
data.alert.vehicleName | string | |
data.alert.subject | string | task:<id> or zone:<id> for geofences; crash:<crs_id> for a crash. |
data.alert.state | string | Required. One of: open, acknowledged, resolved. |
data.alert.detail | object | Required. position, at, taskId, area, zoneId, zoneName, lastSeenAt, since…; a crash: crashId, at, position, accuracyM, speedBeforeKmh, road, driverName, driverPhone (when you have it), taskId, taskRef, taskLabel, state (help | resolved), resolvedHow (ok | help_here). |
data.alert.startedAt | string (date-time) | Required. |
data.alert.notifiedAt | string (date-time) | null | |
data.alert.notifications | integer | |
data.alert.acknowledgedBy | string | |
data.alert.acknowledgedAt | string (date-time) | null | |
data.alert.resolvedBy | string | |
data.alert.resolvedAt | string (date-time) | null | |
data.alert.autoResolved | boolean | Cleared by itself (back inside, heard from again, moving). |
data.changed | string[] | task.updated: the fields the edit changed. |
{
"id": "evt_9bQ2xLm0aZc4RwYeT7Kp",
"type": "string",
"version": "2026-10-16",
"createdAt": "2026-10-15T09:00:00+01:00",
"data": {
"task": {
"id": "string",
"ref": "string",
"status": "created",
"vehicleId": "string",
"pickup": {
"lat": 0,
"lng": 0,
"name": "string",
"address": "string"
},
"dropoff": {
"lat": 0,
"lng": 0,
"name": "string",
"address": "string"
},
"recipientName": "string",
"recipientPhone": "string",
"notes": "string",
"lang": "en",
"arrivingS": 0,
"eta": {
"at": "2026-10-15T09:00:00+01:00",
"durationS": 0,
"distanceM": 0,
"trafficDelayS": 0,
"pickupAt": "2026-10-15T09:00:00+01:00",
"updatedAt": "2026-10-15T09:00:00+01:00"
},
"dueAt": "2026-10-15T09:00:00+01:00",
"late": true,
"trackingUrl": "string",
"trackingExpiresAt": "2026-10-15T09:00:00+01:00",
"failureReason": "string",
"createdAt": "2026-10-15T09:00:00+01:00",
"assignedAt": "2026-10-15T09:00:00+01:00",
"pickedUpAt": "2026-10-15T09:00:00+01:00",
"arrivingAt": "2026-10-15T09:00:00+01:00",
"endedAt": "2026-10-15T09:00:00+01:00",
"area": {
"type": "circle",
"lat": 6.48,
"lng": 3.43,
"radiusM": 2500
},
"proofs": [
{
"id": "string",
"kind": "photo",
"contentType": "image/jpeg",
"bytes": 0,
"lat": 0,
"lng": 0,
"by": "string",
"createdAt": "2026-10-15T09:00:00+01:00"
}
],
"endNote": "string",
"recipientEmail": "string",
"proofRequired": [
"photo"
],
"proofRequiredOverride": [
"string"
],
"packageSize": "small",
"itemDescription": "string",
"reference": "string",
"scheduledAt": "2026-10-15T09:00:00+01:00",
"connectionId": "string",
"acceptedAt": "2026-10-15T09:00:00+01:00",
"startedAt": "2026-10-15T09:00:00+01:00",
"editable": "all",
"editedAt": "2026-10-15T09:00:00+01:00",
"editedFields": [
"string"
],
"pinDelivery": {
"channel": "whatsapp",
"state": "sending",
"whatsappErrorCode": 0,
"needsEmail": true,
"updatedAt": "2026-10-15T09:00:00+01:00"
}
},
"vehicle": "string",
"zone": {
"id": "string",
"name": "string"
},
"at": "2026-10-15T09:00:00+01:00",
"position": {
"lat": 0,
"lng": 0
},
"alert": {
"id": "alr_8KwP2nVd5T",
"ruleId": "string",
"ruleName": "string",
"kind": "geofence_exit",
"vehicleId": "string",
"vehicleName": "string",
"subject": "string",
"state": "open",
"detail": {},
"startedAt": "2026-10-15T09:00:00+01:00",
"notifiedAt": "2026-10-15T09:00:00+01:00",
"notifications": 0,
"acknowledgedBy": "string",
"acknowledgedAt": "2026-10-15T09:00:00+01:00",
"resolvedBy": "string",
"resolvedAt": "2026-10-15T09:00:00+01:00",
"autoResolved": true
},
"changed": [
"string"
]
}
}Response
Received