Agent skill

Ship24 Webhooks Skill

Receive and authenticate Ship24 parcel tracking webhooks. Use when setting up a Ship24 webhook endpoint, checking the Authorization: Bearer webhook secret, debugging 401s, or handling tracking/events and tracking/pod (proof of delivery) messages.

Install this skill

npx skills add hookdeck/webhook-skills --skill ship24-webhooks


Ship24 is a multi-carrier parcel tracking API. When a tracker you follow gets a new tracking event (or a proof of delivery), Ship24 POSTs a JSON message to your Webhook URL. Webhooks are not signed. Ship24 allocates a random Webhook Secret to your account and sends it on every request as Authorization: Bearer <webhook secret>; your endpoint authenticates the sender by comparing it with the secret shown in your Ship24 dashboard.

When to Use This Skill

  • How do I receive Ship24 webhooks?
  • How do I verify the Authorization: Bearer header on a Ship24 webhook?
  • Does Ship24 sign webhook payloads? (No: use the shared Webhook Secret)
  • How do I handle tracking/events and tracking/pod messages?
  • How do I deduplicate Ship24 messages by messageId?
  • Why does my Ship24 webhook endpoint return 401?

Setup Note

Paste the Webhook Secret from your Ship24 dashboard.

Store just the secret (without Bearer ) as SHIP24_WEBHOOK_SECRET. See references/setup.md.

Verification (core)

const crypto = require('crypto');

function verifyShip24Request(headers, configured = process.env.SHIP24_WEBHOOK_SECRET) {
  if (!configured) return false;                              // never accept when unconfigured
  const secret = Buffer.from(configured.replace(/^bearer /i, ''), 'utf8'); // bare secret
  if (!secret.length) return false; // a configured value of just "Bearer " must not match
  const header = headers['authorization'];
  if (typeof header !== 'string' || header.slice(0, 7).toLowerCase() !== 'bearer ') return false;
  const received = Buffer.from(header.slice(7), 'latin1');    // scheme is case-insensitive
  return secret.length > 0 && received.length === secret.length &&
    crypto.timingSafeEqual(received, secret);
}
  • Header: Authorization: Bearer <secret>. The Bearer scheme matches in any case (RFC 9110); the secret itself is compared exactly, never trimmed.
  • Missing header, wrong scheme or mismatch: 401. Body that isn't { "trackings": [...] }: 400.
  • Python: hmac.compare_digest on bytes (encode both sides; str raises on non-ASCII).
  • Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse it only after the check.

What this does and doesn't give you. A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Anyone who captures one request, or learns the secret, can send you arbitrary tracking data. Use HTTPS only and rotate the Webhook Secret if it leaks.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Message Shape and Topics

The body is { "trackings": [ ... ] }. Each item has metadata (generatedAt, messageId, topic), tracker, shipment, events and statistics. Loop over trackings[] (it holds one item unless you raised "Maximum number of updates per webhook message") and dispatch on metadata.topic:

metadata.topicSent WhenWebhook URL
tracking/eventsA tracker has a new tracking eventWebhook URL
tracking/podA proof of delivery was retrieved or is unavailable (Proof of Delivery add-on)Proof of Delivery Webhook URL (can be the same endpoint)
  • Dedupe on metadata.messageId. Ship24 retries a message that didn't get a 2xx, so you can see the same messageId twice.
  • Ordering isn't guaranteed. Compare each event's occurrenceDatetime with what you already stored before changing a shipment's status.
  • Respond with any 2xx. Otherwise the request is retried up to 20 times with exponential backoff (seconds to hours). Proof of delivery messages always contain exactly one tracking.

Environment Variables

SHIP24_WEBHOOK_SECRET=your_webhook_secret   # Ship24 dashboard > Webhook Secret (without "Bearer ")

Local Development

Ship24 can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 ship24 --path /webhooks/ship24

Use the printed URL as the Webhook URL in your Ship24 dashboard.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 11, 2026

View on GitHub →