Gareth Wilson Gareth Wilson

Guide to Ship24 Webhooks: Features and Best Practices

Published


Ship24 webhooks push parcel tracking updates to your system as couriers report them. Ship24 tracks shipments across many carriers through one API, and once you create a Tracker for a shipment, every new event the courier reports, such as in transit, out for delivery or delivered, can reach your order system or customer notifications straight away instead of on your next poll.

This guide covers how Ship24 webhooks are set up and authenticated, the tracking and proof-of-delivery messages and their shape, how grouping, ordering and retries behave, and the practices that keep a Ship24 receiver reliable.

What are Ship24 webhooks?

Ship24 sends webhook messages as soon as a valid Webhook URL is configured in your dashboard. From then on, it pushes any new tracking events found by your Trackers to that URL as JSON POST requests. Each message contains the entire event, so your system can use the data without calling the API.

Ship24 does not sign webhooks. It allocates a random Webhook Secret to your account and sends it with every request as Authorization: Bearer <secret>. Your endpoint compares the value with the one shown in your dashboard.

Ship24 webhook features

FeatureDetails
ConfigurationWebhook URL in the Ship24 dashboard. Proof of Delivery needs a separate URL on the same page
Method and formatPOST, JSON body
AuthenticationAuthorization: Bearer <Webhook Secret>, allocated per account
Signature and timestampNone
Topicstracking/events and tracking/pod, in metadata.topic
GroupingOne update per message by default. Grouping, on request, adds about 15 minutes of delay
OrderingNot guaranteed
Success responseAny 2xx. The response body does not matter
RetriesUp to 20, with exponential backoff from a few seconds to a few hours
ResendAll messages for a tracker, through the API's resend webhooks endpoint
TestingTest your integration in the dashboard sends dummy tracking events
Source IPs54.161.7.2. Dashboard test sends come from elsewhere

Tracking and proof-of-delivery messages

Every message has a trackings array. By default on recent accounts it holds exactly one item, and that item's events array holds exactly one event. Here is Ship24's example, with the parts it elides marked:

{
  "trackings": [
    {
      "metadata": {
        "generatedAt": "2025-03-04T17:13:35.000Z",
        "messageId": "356a7f93-3ce5-4b49-b560-156537283df9",
        "topic": "tracking/events"
      },
      "tracker": {
        "trackerId": "26148317-7502-d3ac-44a9-546d240ac0dd",
        "trackingNumber": "9400115901047177598206",
        "shipmentReference": "c6e4fef4-a816-b68f-4024-3b7e4c5a9f81",
        "clientTrackerId": "3fa99515-3ca0-4901-85bb-056ee016799b",
        "isSubscribed": true,
        "createdAt": "2025-03-01T03:16:22.000Z"
      },
      "shipment": { "shipmentId": "f4f888d7-d140-423f-9a48-e0689d27e098", "...": "..." },
      "events": [
        {
          "eventId": "ee8ebe96-4eae-4a91-9a99-8f3afa6a0f46",
          "status": "Delivered to the addressee",
          "occurrenceDatetime": "2025-03-04T17:12:57",
          "...": "..."
        }
      ]
    }
  ]
}

metadata.messageId identifies the message, tracker identifies the Tracker and its tracking number, and occurrenceDatetime is when the event happened. Some fields belong to add-on options and are absent when you are not subscribed to them, so your parser must tolerate missing keys.

With the Proof of Delivery add-on, Ship24 asks the courier for a proof once a shipment is delivered, and sends one message with topic tracking/pod to your Proof of Delivery Webhook URL. Its data.status is found or unavailable. When found, data.content holds either text, such as a signatory name, or a downloadUrl for a PDF or image. The download URL is valid for 7 days. Proof-of-delivery messages are never grouped.

Inspect Ship24 webhooks as they arrive. Set your Webhook URL to a Hookdeck Inspect URL and use Test your integration to see a message's headers and body, with no account or setup required.

Setting up Ship24 webhooks

  1. In the Ship24 dashboard, set your Webhook URL to your HTTPS endpoint.
  2. If you subscribe to Proof of Delivery, set the Proof of Delivery Webhook URL on the same page. It can be the same endpoint or a different one. Without it, no proof-of-delivery messages are sent.
  3. Copy the Webhook Secret shown in the dashboard and store it server-side, for example as SHIP24_WEBHOOK_SECRET.
  4. Use Test your integration to send a message with dummy tracking events.

To route through Hookdeck, create a Hookdeck source with the Ship24 type and use its URL as the Webhook URL, and as the Proof of Delivery Webhook URL if you use it. The source has one field, Webhook Secret: paste the Webhook Secret from your Ship24 dashboard. Paste only the secret. Ship24 sends it as Bearer <secret> in the Authorization header, and Hookdeck matches the Bearer scheme itself. The source accepts POST only, which is the method Ship24 uses.

If you restrict inbound traffic by IP on your own endpoint, set it up after testing: dashboard test sends do not come from 54.161.7.2.

Securing Ship24 webhooks

Read the Authorization header, check that the scheme is Bearer (case-insensitively), and compare the rest with your Webhook Secret in constant time.

const crypto = require('crypto');
const express = require('express');

const WEBHOOK_SECRET = process.env.SHIP24_WEBHOOK_SECRET; // the secret only, no "Bearer "

function verifyBearer(header) {
  if (!WEBHOOK_SECRET || typeof header !== 'string') return false;
  const match = /^bearer (.+)$/i.exec(header);
  if (!match) return false;
  const received = Buffer.from(match[1], 'utf8');
  const expected = Buffer.from(WEBHOOK_SECRET, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/ship24', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifyBearer(req.get('authorization'))) {
    return res.sendStatus(401);
  }

  let message;
  try {
    message = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  for (const tracking of message.trackings || []) {
    // Route on tracking.metadata.topic; dedupe on tracking.metadata.messageId.
  }
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os

WEBHOOK_SECRET = os.environ.get("SHIP24_WEBHOOK_SECRET", "")  # the secret only


def verify_bearer(header) -> bool:
    if not WEBHOOK_SECRET or not header:
        return False
    scheme, _, token = header.partition(" ")
    if scheme.lower() != "bearer":
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(token.encode("utf-8"), WEBHOOK_SECRET.encode("utf-8"))

The Webhook Secret is static. It authenticates the sender, not the body, and with no timestamp a captured request can be replayed. Serve the endpoint over HTTPS and keep the secret out of logs. Allowlisting 54.161.7.2 adds a second check, but the secret is what authenticates a request.

Make Ship24 webhooks production-ready. Hookdeck Event Gateway checks the Webhook Secret, answers Ship24 straight away, and queues every tracking update durably before your service sees it.

Ship24 webhook limitations and pain points

Updates arrive out of order

The Problem: A shipment shows as in transit after it has been delivered.

Why It Happens: Ship24 does not guarantee order. A retried message can arrive after newer ones, and couriers can report events for one shipment at different times.

Workarounds:

  • Before changing a shipment's status, compare the event's occurrenceDatetime with the latest one you already have, as Ship24 recommends, and ignore older events.
  • Store every event, but derive the current status from the latest by occurrence time.

How Hookdeck Can Help: Hookdeck keeps every message with its full body, so when a status looks wrong you can see the order things actually arrived in and replay them after fixing your logic.

Retries mean duplicates

The Problem: The same tracking event is processed twice, and a customer gets two "delivered" emails.

Why It Happens: When Ship24 does not receive a 2xx, it retries the message up to 20 times, with delays growing from a few seconds to a few hours.

Workarounds:

  • Store each metadata.messageId, or the event's eventId, before acting on it, and skip repeats.
  • Make notifications to customers idempotent per shipment and status.

How Hookdeck Can Help: Hookdeck deduplication can drop repeated messages before they reach your service, based on messageId or eventId.

Proof-of-delivery files expire

The Problem: A proof of delivery you meant to keep is gone when someone asks for it.

Why It Happens: The downloadUrl in a tracking/pod message is valid for 7 days, and proofs are only delivered by webhook, not through the tracking API.

Workarounds:

  • Download the file and store it on your side as soon as the message arrives.
  • If your service was down, use the API's resend webhooks endpoint for the tracker while the link is still valid.

How Hookdeck Can Help: Hookdeck stores every tracking/pod message and retries delivery to your service on a schedule you control, so a brief outage does not leave you without the link.

Grouped messages need different handling

The Problem: A handler that reads only trackings[0] drops updates.

Why It Happens: Accounts with grouping enabled can receive several updates, for several shipments, in one message, and Ship24 waits about 15 minutes to group them.

Workarounds:

  • Always loop over trackings, even on a default account.
  • Keep the default of one update per message unless the volume of requests is a real problem.

How Hookdeck Can Help: Hookdeck transformations can reshape a message before it reaches your service, and every original message is kept for inspection.

Best practices

Store the secret, not the header

Keep only the secret in your configuration and match the Bearer scheme in code, the same way the Hookdeck source does.

Tolerate missing fields

Add-on fields are absent when you are not subscribed to them. Use optional access throughout and never require a field the core payload does not guarantee.

Make handlers idempotent

Key stored events on eventId and messages on messageId. See our guide to webhook idempotency.

Acknowledge fast and process asynchronously

Verify, store, return 200, and do the work afterwards. See why to process webhooks asynchronously.

Conclusion

Ship24 pushes tracking updates and proofs of delivery as JSON POST requests with your account's Webhook Secret in an Authorization: Bearer header and no signature. Check the scheme and compare the secret in constant time over HTTPS, loop over trackings, compare occurrenceDatetime before changing status, deduplicate retried messages, and store proof-of-delivery files before their links expire.

Hookdeck Event Gateway checks the Webhook Secret, answers Ship24 immediately and keeps every message until your service has processed it, so an outage on your side does not mean missed deliveries.

Get started with Hookdeck for free and handle Ship24 webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.