Skip to Content

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

EventDescription
tracking.updatedShipment status changed
tracking.deliveredShipment successfully delivered
tracking.exceptionShipping issue or failed delivery attempt
shipment.createdNew 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:

HeaderDescriptionExample
Webhook-TimestampUnix timestamp (seconds) when the request was sent1733678400
Webhook-SignatureHMAC-SHA256 signature of the payloadt=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

  1. Concatenate the timestamp and raw request body: {timestamp}.{raw_body}
  2. Compute HMAC-SHA256 of that string using your shared webhook secret
  3. Encode the result as a lowercase hex string
  4. The Webhook-Signature header is formatted as t=<timestamp>,v1=<hex>

Verification steps

  1. Check both headers are present — return 401 if missing
  2. Reject the request if the timestamp is older than 300 seconds
  3. Extract the v1= value from Webhook-Signature
  4. Recompute the expected signature using the raw body and your secret
  5. Compare using a timing-safe equality function
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:

CodeMeaning
200 OKEvent received and processed successfully
401 UnauthorizedSignature verification failed
500 Internal Server ErrorProcessing 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