Webhooks
Webhooks allow you to receive real-time HTTP notifications when shipment events occur. Each request is signed with HMAC-SHA256 so you can verify it genuinely came from Spedisci.online.
Events
| Event | Description |
|---|---|
tracking.updated | Shipment status changed |
tracking.delivered | Shipment successfully delivered |
tracking.exception | Shipping issue or failed delivery attempt |
shipment.created | New shipment created |
Payload
Every webhook POST request contains a JSON body with the following structure:
{
"event": "tracking.updated",
"timestamp": 1733678400,
"data": {
"tracking_number": "DEMO05407027",
"carrier": "BRT",
"status": "In Transit",
"location": "Trento",
"events": [
{
"timestamp": "22/09/2016 15:22",
"status": "Arrivata in sede destinataria",
"location": "Trento"
}
]
}
}Request Headers
Each webhook request includes two security headers:
| Header | Description | Example |
|---|---|---|
Webhook-Timestamp | Unix timestamp (seconds) when the request was sent | 1733678400 |
Webhook-Signature | HMAC-SHA256 signature of the payload | t=1733678400,v1=4f8a9b2c...d7e1f3 |
Verifying Signatures
To confirm a webhook is genuine, verify the HMAC-SHA256 signature on every incoming request.
How the signature is constructed
- Concatenate the timestamp and raw request body:
{timestamp}.{raw_body} - Compute
HMAC-SHA256of that string using your shared webhook secret - Encode the result as a lowercase hex string
- The
Webhook-Signatureheader is formatted ast=<timestamp>,v1=<hex>
Verification steps
- Check both headers are present — return
401if missing - Reject the request if the timestamp is older than 300 seconds
- Extract the
v1=value fromWebhook-Signature - Recompute the expected signature using the raw body and your secret
- Compare using a timing-safe equality function
Node.js
const crypto = require('crypto')
function verifyWebhook(req, webhookSecret) {
const timestamp = req.headers['webhook-timestamp']
const signatureHeader = req.headers['webhook-signature']
if (!timestamp || !signatureHeader) {
return false // Missing headers
}
// Reject requests older than 5 minutes
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10)
if (age > 300) {
return false
}
// Extract the v1 signature
const v1 = signatureHeader.split(',').find(p => p.startsWith('v1='))?.slice(3)
if (!v1) return false
// Recompute expected signature
const payload = `${timestamp}.${req.rawBody}`
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex')
// Timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(v1),
Buffer.from(expected)
)
}Responding to Webhooks
Your endpoint must respond within a reasonable time. Return an appropriate HTTP status code:
| Code | Meaning |
|---|---|
200 OK | Event received and processed successfully |
401 Unauthorized | Signature verification failed |
500 Internal Server Error | Processing error |
Failed deliveries (non-200 responses or timeouts) are retried automatically with exponential backoff.
Always verify signatures using a timing-safe comparison to prevent timing attacks. Never use == or === for comparing HMAC values.
Last updated on