# Guide to Flexport Webhooks: Features and Best Practices

Flexport webhooks notify your systems about freight milestones: a shipment is created, a shipment leg departs, a booking updates. If you're building logistics visibility on Flexport, webhooks are how you react to milestones without polling.

This guide covers how Flexport (classic freight) webhooks work, the events you'll handle, how to verify the `X-Hub-Signature` headers, and the best practices for production.

## What are Flexport webhooks?

Flexport freight webhooks are HTTP POSTs delivered to an endpoint you configure, each carrying a Flexport "Event" object. Flexport signs deliveries GitHub/X-Hub style, with two headers keyed on a per-endpoint secret token: `X-Hub-Signature` (HMAC-SHA1, being deprecated) and `X-Hub-Signature-256` (HMAC-SHA256, recommended).

This guide covers the classic freight webhooks. Flexport also has a newer Logistics API with its own webhook endpoints and `Order.*`/`Shipment.*` events; if your integration is on that product, its scheme and envelope differ.

## Flexport webhook features

| Feature | Details |
| --- | --- |
| Configuration | Account Settings (UI-created endpoints with a secret token) |
| Signature headers | `X-Hub-Signature` (SHA1, deprecating), `X-Hub-Signature-256` (SHA256, recommended) |
| Signature scheme | `sha256=<hex>` HMAC of the raw body, keyed with the per-endpoint secret token |
| Event format | `/object#event` (e.g. `/shipment#created`) |
| Payload | A Flexport "Event" object |
| Acknowledgement | Return HTTP 200 (respond fast, process async) |
| SDK | None |

## Common events

Flexport milestone identifiers use the `/object#event` format (not `object.event`):

| Event | Fires when |
| --- | --- |
| `/shipment#created` | A shipment is created |
| `/shipment_leg#departed` | A shipment leg departs |
| `/shipment#updated` | A shipment updates |
| `/booking#created` | A booking is created |

Branch on the `/object#event` identifier. Some milestones are available upon request. Consult Flexport's Milestones reference for the full list.

## Setting up Flexport webhooks

Configure endpoints and their secret token in the account Settings section (classic freight uses UI creation rather than a public create-endpoint API). Your endpoint must return HTTP 200; respond fast and process asynchronously, since some milestones can be heavy.

## Securing Flexport webhooks

Each delivery carries `X-Hub-Signature` (SHA1) and `X-Hub-Signature-256` (SHA256). Prefer the SHA256 header: it's `sha256=` followed by a hex HMAC-SHA256 of the raw request body, keyed with your per-endpoint secret token. Verify against the raw bytes and compare in constant time.

```javascript
const crypto = require("crypto");

const SECRET = process.env.FLEXPORT_WEBHOOK_SECRET; // per-endpoint secret token

function verify(rawBody, signatureHeader) {
  const expected = "sha256=" +
    crypto.createHmac("sha256", SECRET).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-hub-signature-256"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge fast
  processQueue.add(JSON.parse(req.body)); // process asynchronously
});

```

The same check in Python:

```python
import hashlib
import hmac
import os

SECRET = os.environ["FLEXPORT_WEBHOOK_SECRET"].encode()

def verify(raw_body: bytes, signature_header: str) -> bool:
    expected = "sha256=" + hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")

```

## Flexport webhook limitations and pain points

### SHA1 vs SHA256

The Problem: Flexport sends both `X-Hub-Signature` (SHA1) and `X-Hub-Signature-256` (SHA256). Verifying the deprecated SHA1 header ties you to a weaker, sunsetting scheme.

Why It Happens: Flexport keeps the legacy SHA1 header for compatibility while recommending SHA256.

Workarounds:

* Verify the `X-Hub-Signature-256` header and treat SHA1 as legacy.

How Hookdeck Can Help: Hookdeck verifies the SHA256 signature at the edge, so your app receives pre-verified events without depending on the deprecated header.

### The `/object#event` naming

The Problem: Milestone identifiers use `/object#event` (e.g. `/shipment#created`), not the `object.event` form many integrations assume. Matching the wrong format silently ignores events.

Why It Happens: Flexport's milestone reference uses the `/object#event` convention.

Workarounds:

* Branch on the exact `/object#event` identifier from the payload.

How Hookdeck Can Help: Hookdeck's filters route on the fields you actually receive, so the naming convention is handled in one place.

### UI-only endpoint creation

The Problem: Classic freight endpoints and their secret tokens are created in the Settings UI, not via a public API, which complicates programmatic setup and environment parity.

Why It Happens: The classic product manages endpoints through the dashboard.

Workarounds:

* Document endpoint configuration and secrets as part of your setup runbook.

How Hookdeck Can Help: Hookdeck gives you an API-manageable ingestion URL in front of Flexport, so your side of the configuration is reproducible.

### Raw-body handling and duplicates

The Problem: The signature is over the raw body, and deliveries can repeat, so re-serializing the body breaks verification and duplicates can double-process.

Why It Happens: The signed content is the exact bytes; at-least-once delivery brings duplicates.

Workarounds:

* Verify against the raw body and dedupe on an event id.

How Hookdeck Can Help: Hookdeck verifies against the bytes as received and deduplicates. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

## Best practices

### Verify `X-Hub-Signature-256` over the raw body

Use `sha256=` + hex HMAC-SHA256 over the raw body with the per-endpoint secret, and compare in constant time. Treat SHA1 as legacy.

### Branch on `/object#event`

Match the exact `/object#event` identifier, not `object.event`.

### Acknowledge fast, process asynchronously

Return 200 immediately and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Dedupe

Dedupe on an event id so repeats don't double-process.

## Conclusion

Flexport freight webhooks are signed GitHub/X-Hub style, verify the `X-Hub-Signature-256` (SHA256) header over the raw body with your per-endpoint secret and treat SHA1 as legacy. Branch on the `/object#event` milestone identifiers, acknowledge fast, and dedupe.

[Hookdeck](https://hookdeck.com) verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique milestones.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Flexport webhooks reliably in minutes.