Skip to main content
Outgoing webhooks push Shipment tracking updates to your systems the moment Perform.AI processes new events — no polling, no rate-budget cost. This guide covers the full lifecycle: configuring your endpoint, choosing triggers, verifying requests, and reading the payload.

Set up your webhook endpoint

Webhooks are configured in the portal under Integrations > Webhooks:
1

Prepare a receiving endpoint

A publicly reachable HTTPS URL that accepts POST requests with a JSON body and responds 200 quickly. Perform.AI must be able to reach it directly — allowlist by hostname rather than IP if you filter inbound traffic.
2

Register the endpoint

In Integrations > Webhooks, add your endpoint URL.
3

Copy the Authentication Key

The page provides the Authentication Key used to sign every webhook with HMAC-SHA256 — store it in your secret manager; you’ll verify signatures with it (below). HMAC is the default scheme; if you need a different one (OAuth 2.0, JWT, Basic, static key), raise it with your account team.
4

Activate triggers

Choose which notification triggers fire webhooks (next section). No active triggers means no webhooks.
The payload format version (5.0 / 5.2 / 5.3) is set per account, not per endpoint — confirm or change yours with your account team before integrating, and parse accordingly.

Triggers

Triggers define when a webhook fires — on every event, or only on specific milestones. They’re managed with your notification configuration in the portal. Each delivered payload’s trigger array names every activated trigger that the update cycle satisfied. Values derive from the trigger’s name (lowercased, punctuation replaced by underscores). Standard triggers include: Accounts using Returns also have return-lifecycle triggers (for example returns_all_events), and custom triggers can be set up with your account team — their payload values follow the same naming derivation (for example a “Successfully delivered + all events” trigger emits successfully_delivered_all_events). One webhook per update cycle: when several activated triggers fire from the same batch of new events, you receive a single webhook whose trigger array lists them all — deduplicate by shipment and updated_date, not by trigger.

How delivery works

  • HTTPS POST with a JSON body.
  • Respond with HTTP 200. Anything else — including a timeout — counts as a failure: Perform.AI retries up to 3 times, 5 minutes apart, then stops. Missed webhooks are not replayed afterwards; reconcile gaps with the List Shipments API filtered by updated_date.
Acknowledge fast, process async: return the 200 immediately on receipt and queue the payload. Slow handlers cause timeouts, which burn your three retries.

Verify the sender

Every request carries an HTTP-X-Hmac-SHA256 header: the HMAC-SHA256 of the raw request body, keyed with your Authentication Key. Recompute and compare:
Compute over the raw bytes — re-serializing the parsed JSON changes the digest. Reject non-matching requests.

The payload

data carries the full Shipment — identifiers, carrier, status and phase, addresses, costs, documents — plus three event views: The field-by-field payload is documented per format version in the reference:

Format 5.0

The baseline payload.

Format 5.2

Adds returns, line items, collection point, rating, enriched event locations.

Format 5.3

Restructured EDD by source + Performance Aspiration results.

Format versions

Formats are additive on the same envelope — build your parser to ignore unknown fields and upgrades are non-breaking, with one exception: 5.3 restructures expected_delivery (below).

Field behaviors worth knowing

  • Event timestamps vary in precision. Each event’s time reflects what the source provided — full timestamp with offset, timestamp without timezone, or date only — with timezone set only when known. See timestamps.
  • expected_delivery (formats 5.0/5.2) is a simple from/to window and is omitted entirely when neither merchant nor carrier provided an EDD — don’t assume the key exists.
  • expected_delivery (format 5.3) becomes four source objects — merchant, carrier, predict_engine, and display — and unlike earlier formats the object is always present, with a source set to null when it has no estimate (the value shown in the product, following your account’s configurable EDD display priority). Each carries start, end, a type, and timezones. Precision differs by source: merchant and carrier estimates are always date-only ranges (type: date_range — no time component, and merchant timezones are always Etc/UTC), while predict_engine provides full datetime ranges (datetime_range). Check type before parsing rather than assuming a time exists.
  • pa_results (format 5.3) is an empty array unless the Performance Aspiration module is enabled on your account. When populated, expected_result_date can be null for hour-based transit-time rules.
  • Units are normalized: dimensions always in centimeters, weight always in grams, money fields as {amount, currency} objects.
  • notification_type is currently always shipment_notification — treat it as a discriminator for future notification types, not information.
  • Address objects mirror the Shipment’s stored addresses; their email/phone are contact data, not notification subscriptions.

Test your integration

Webhooks fire on real update cycles, so the simplest end-to-end test needs no carrier: create a test Shipment with a tracking number and carrier, then push events yourself with the Create Events API — each call is an update cycle that delivers a webhook to your endpoint. Verify your HMAC check against the received header before going live, and test your non-200 handling by temporarily returning an error to observe the retry behavior.