Guide to Nash Webhooks: Features and Best Practices
Nash webhooks push delivery progress to your systems as it happens: a delivery is created, a driver is assigned, the courier arrives at pickup, the parcel is dropped off, or a provider cancels. If you dispatch local deliveries through Nash and want to update customers, advance orders or alert on problems without polling, webhooks are how Nash tells you what it already knows.
This guide covers how Nash endpoints are set up and signed, the events Nash sends and what their payloads contain, how retries work, and the best practices for running a Nash receiver in production.
What are Nash webhooks?
Nash sends webhooks through Svix. You register endpoints in the Nash Portal, choose which event types each one receives, and Nash sends a JSON POST for every matching event, signed with the Svix headers svix-id, svix-timestamp and svix-signature.
Every payload has three fields:
type: the kind of event, one ofdelivery,task,courier_location,courier_nameorshiftevent: the status within that type, such asdropoff_completedata: fordeliveryandtaskevents, the whole Job object, in the same shape as the Create Job response. Forshiftevents, a small payload of its own
Together, type and event name the event, so delivery with dropoff_complete is delivery.dropoff_.
Nash webhook features
| Feature | Details |
|---|---|
| Configuration | Nash Portal: Settings > Webhook Management |
| Endpoints | Multiple endpoints, each with its own event type filter |
| Environments | Sandbox and production |
| Event Catalog | A Portal tab with every event schema and example payloads for some events |
| Signature headers | svix-id, svix-timestamp, svix-signature |
| Scheme | HMAC-SHA256, base64, over {svix-id}.{svix-timestamp}.{raw body} |
| Secret | One per endpoint, whsec_ prefixed |
| Event name | type and event in the body |
| Event ID | None in the body. svix-id stays the same when a message is resent |
| Payload | A snapshot of the whole Job, not a diff |
| Retries | Immediately, then 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours |
| Recovery | Retry single messages, or recover all failed messages from a date, in the Portal |
| Delivery control | webhook-delivery: abort-message stops retries. webhook-delivery: disable or 410 Gone disables the endpoint |
| Source IPs | Svix's published source IP list |
Nash does not state a response timeout for its endpoints.
Common events
Delivery events
Delivery events are sent once per state transition. Nash lists 27, including:
created,assigned_driver,not_assigned_driver,startedpickup_enroute,pickup_arrived,pickup_completedropoff_enroute,dropoff_arrived,dropoff_completecanceled_by_provider,canceled_by_customer,canceled_by_nash,failed,expiredreturn_in_progress,returnedproof_of_delivery,pincode_verification,pickup_label, and the shipping-onlyshipping_labelandmanifested
Seven of them are guaranteed: created, assigned_driver, pickup_enroute, pickup_arrived, dropoff_enroute, dropoff_arrived and dropoff_complete. Nash notes that guaranteed events "may be inferred from future events", so one can arrive later than the change it describes.
Task events
Task events track each delivery attempt: created, updated, running, completed, failed, the cancellation events including canceled_by_reassign and canceled_, dispatch_failure, planned_selection, refund_created and refund_updated.
Courier events
courier_location.updated is sent every 1 to 2 minutes while a courier location is available. With immediate location webhooks turned on, it is sent as each position arrives, at most once every 20 seconds per delivery, which Nash calls "several times the volume". courier_name.updated is sent when the same provider swaps drivers.
Shift events
shift.dispatched has a short payload:
{
"type": "shift",
"event": "dispatched",
"data": {
"eventSourceId": "act_7xK9mP2nQ4wL",
"created_job_ids": ["job_8rY3nK7pL2xM", "job_5tW9mN3kP8yQ"],
"errors": {}
}
}
Delivery, task and courier events carry the full Job in data, with its jobConfigurations, tasks, quotes, the delivery's status, courier details, documents and statusHistory. All timestamps are UTC ISO 8601 with microseconds and no time zone suffix, such as 2022-03-05T01:11:51.078927. Parse them as UTC.
Setting up Nash webhooks
- In the Nash Portal, open Settings > Webhook Management.
- Add an endpoint with your HTTPS URL and choose the event types it should receive. You can add more endpoints to route different events to different services.
- Open the endpoint and copy its Signing Secret. Store it server-side, for example as
NASH_WEBHOOK_SECRET. - Use the Event Catalog tab to see each event's schema.
- Set up a sandbox endpoint first and create test jobs against the sandbox API to see real events.
If your endpoint is behind a firewall, allow traffic from the IP addresses in Svix's source IP documentation.
Securing Nash webhooks
Nash recommends the Svix SDKs, or manual verification following Svix's documented scheme. Build the signed content as {svix-id}.{svix-timestamp}.{raw body}, compute an HMAC-SHA256 with the base64-decoded part of your secret after whsec_, base64-encode it, and compare it in constant time with each v1, entry in svix-signature.
const crypto = require('crypto');
const express = require('express');
function verifyNashSignature(rawBody, headers, secret) {
const id = headers['svix-id'];
const timestamp = headers['svix-timestamp'];
const signatureHeader = headers['svix-signature'];
if (!id || !timestamp || !signatureHeader) return false;
if (typeof secret !== 'string' || !secret.startsWith('whsec_')) return false;
// The key is the base64-decoded part of the secret after "whsec_".
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const expected = crypto
.createHmac('sha256', key)
.update(`${id}.${timestamp}.`)
.update(rawBody)
.digest();
// The header can hold several space-separated "v1,<base64>" entries.
return signatureHeader.split(' ').some((entry) => {
const [version, signature] = entry.split(',');
if (version !== 'v1' || !signature) return false;
const received = Buffer.from(signature, 'base64');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
const app = express();
app.post('/webhooks/nash', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.NASH_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyNashSignature(req.body, req.headers, secret)) {
return res.sendStatus(401);
}
// Optional: if your own policy needs it, reject svix-timestamp values that are too old.
const event = JSON.parse(req.body.toString('utf8'));
// Route on `${event.type}.${event.event}`, deduplicate on svix-id, then process async.
res.sendStatus(200);
});
The same check in Python:
import base64
import hashlib
import hmac
def verify_nash_signature(raw_body: bytes, headers, secret: str) -> bool:
msg_id = headers.get("svix-id")
timestamp = headers.get("svix-timestamp")
signature_header = headers.get("svix-signature")
if not (msg_id and timestamp and signature_header):
return False
if not isinstance(secret, str) or not secret.startswith("whsec_"):
return False
key = base64.b64decode(secret[len("whsec_"):])
signed_content = f"{msg_id}.{timestamp}.".encode() + raw_body
expected = base64.b64encode(hmac.new(key, signed_content, hashlib.sha256).digest())
for entry in signature_header.split(" "):
version, _, signature = entry.partition(",")
if version == "v1" and hmac.compare_digest(signature.encode(), expected):
return True
return False
The Svix SDKs also check that svix-timestamp is recent. If you verify by hand, Nash says you "will also need to implement the timestamp check yourself", so add one if your policy calls for it.
To check your verifier, use the secret whsec_, the headers svix-id: msg_ and svix-timestamp: 1761040800, and this exact body:
{"type":"delivery","event":"dropoff_complete","data":{"id":"job_R7FXJrfB99bpvpzf6CriJC"}}
The svix-signature header should be:
v1,T+jidJZejn4D1oZjobN/P3MGx5j/XsmEi22BqYeO4o8=
Make Nash webhooks production-ready. Hookdeck Event Gateway verifies the Svix signature on every Nash delivery, absorbs bursts of courier location updates, and queues delivery events durably before your service sees them.
Nash webhook limitations and pain points
The same event can arrive more than once
The Problem: Your system sends a customer two "your driver has arrived" messages for one order.
Why It Happens: A Nash job can have several tasks, one per delivery attempt or provider. Nash warns that "both providers might send pickup_arrived events". Svix retries also resend a message, with the same svix-id, if your endpoint didn't return a 2xx.
Workarounds:
- Deduplicate deliveries on
svix-id. - Deduplicate business actions on the job ID, task ID and event, so a second
pickup_arrivedfor the same job doesn't notify the customer again.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on the fields you choose, such as data.id, type and event.
Providers can report completion too early
The Problem: You mark an order delivered while the driver is still on the way.
Why It Happens: Nash says some providers send statuses such as pickup_complete or dropoff_complete "when their drivers have not completed a delivery yet". Nash passes the status on.
Workarounds:
- Check
proofOfDeliverybefore you treat a delivery as final. - Hold customer-facing "delivered" messages until you have proof, or until a short delay has passed without a contradicting event.
How Hookdeck Can Help: Every event is stored in Hookdeck with its full payload, so you can see exactly which provider status arrived and when.
Reassignment creates a new task
The Problem: A cancellation event makes your system think the whole delivery was cancelled.
Why It Happens: When a job is reassigned, Nash ends the old task with a cancellation event (such as task.canceled_) and creates a new task on the same job. Nothing on the old task points at its replacement.
Workarounds:
- Treat a task cancellation as terminal for that task only.
- Read
jobConfigurations[].advancedTaskto find the attempt that is live.
How Hookdeck Can Help: Hookdeck filters can send task events and delivery events to different handlers, so task cancellations don't reach code that acts on delivery status.
Location updates add volume
The Problem: Your endpoint slows down when many deliveries are active, and the delivery status events that matter queue behind location updates.
Why It Happens: courier_location.updated is sent every 1 to 2 minutes per delivery, and with immediate location webhooks at most every 20 seconds per delivery. Each one carries the whole Job.
Workarounds:
- Send
courier_locationevents to a separate endpoint, using Nash's event type filter. - Store only the latest position per delivery and drop older ones.
How Hookdeck Can Help: Hookdeck queues every event and delivers to your service at the rate you set, so bursts of location updates don't overload your endpoint.
Best practices
Treat each payload as a snapshot
Each payload is the whole job as it was when the change was saved. Events raised by the same change share one snapshot. Compare statusHistory with what you already stored rather than assuming the payload describes only one change.
Handle every task on a job
A cancellation or failure on one task shouldn't disrupt your handling of the job's other tasks.
Return the right status codes
Return a 2xx when you've accepted an event. Svix retries network errors, 5xx responses and 408, 409 and 429. Use webhook-delivery: abort-message to stop retries for a message you will never accept, and avoid 410 Gone unless you want the endpoint disabled.
Acknowledge fast and process asynchronously
Return 200 after verification and queue the work. See why to process webhooks asynchronously.
Make handlers idempotent
Processing the same event twice should have the same result as processing it once. See our guide to webhook idempotency.
Conclusion
Nash sends delivery, task, courier and shift events through Svix, each with type, event and a full Job snapshot in data, signed with an HMAC-SHA256 over svix-id, svix-timestamp and the raw body. Verify with the endpoint's whsec_ secret, deduplicate on svix-id and on the job and event, treat task cancellations as task-level, and check proof of delivery before you tell a customer the order arrived.
Hookdeck Event Gateway checks every Nash signature, smooths out bursts of location updates, and holds each event until your service has processed it.
Get started with Hookdeck for free and handle Nash webhooks reliably in minutes.