Custom API bridge
Use Custom API when your application creates orders through the Orderly API or SDK instead of importing them from a storefront. Each bridge instance can be an order source, an outbound dispatch destination, a shipment-update recipient, or a combination of those roles.
The two-bridge round trip
You can create two Custom API bridges for one end-to-end workflow:
Custom API 1 → creates an order in Orderly
Orderly → dispatches the order to Custom API 2
Custom API 2 → creates or updates the shipment in Orderly
Orderly → pushes that shipment back to Custom API 1
The order is created with bridgeId set to Custom API 1. A dispatcher targets
Custom API 2 and sends order.dispatched. Custom API 2 then calls the Shipments
API using the Orderly orderId. Finally, run or schedule Custom API 1's
push-shipments task to send shipment.updated back to the original source.
Orderly identifies the return destination from the order's original bridgeId,
not the bridge that created the shipment. This is what keeps API 1 and API 2
separate throughout the round trip.
Create the bridge
- Open Bridges → Add Bridge.
- Select Custom API under Custom.
- Give it a recognizable display name, such as "Warehouse API".
- Leave the webhook fields empty for a source-only bridge, or provide both:
- Outbound webhook URL: a public HTTPS endpoint.
- Webhook signing secret: a random value of at least 32 characters.
- Click Connect.
Use this bridge's ID as bridgeId when creating orders. The API key and bridge
must belong to the same organization.
import { Orderly } from '@orderly/sdk'
const orderly = new Orderly({ apiKey: process.env.ORDERLY_API_KEY! })
const { data: order } = await orderly.orders.create({
bridgeId: process.env.ORDERLY_BRIDGE_ID!,
externalId: 'erp-10042',
orderNumber: '10042',
status: 'pending',
paymentStatus: 'paid',
fulfillmentStatus: 'unfulfilled',
customer: {
email: 'jane@example.com',
firstName: 'Jane',
lastName: 'Doe',
},
lineItems: [
{
sku: 'SKU-100',
name: 'Sample product',
quantity: 1,
unitPrice: 49.99,
totalPrice: 49.99,
requiresShipping: true,
taxable: true,
},
],
totals: { subtotal: 49.99, shipping: 0, tax: 0, discount: 0, total: 49.99 },
currency: 'USD',
})
Deliver dispatched orders
After saving a destination and signing secret, open Configure → Outbound Events and enable the events this bridge instance should receive:
order.dispatchedsends orders routed to this bridge by a dispatcher.shipment.updatedsends shipments for orders originally created through this bridge.
For an outbound destination such as Custom API 2, create a dispatcher targeting that bridge. Matching orders are delivered in batches through the dispatch queue.
For the original source such as Custom API 1, open Tasks & Schedules and run
or schedule Push Shipment Updates (push-shipments). The task accepts:
| Field | Meaning |
|---|---|
updatedSince | Optional ISO timestamp; overrides the rolling cursor |
lookbackMinutes | First-run lookback, default 60 and maximum 10,080 |
status | Optional shipment status filter |
limit | Maximum records per run, default 100 and maximum 500 |
After a successful scheduled run, the next run continues from the last exported shipment update. If nothing changed, no empty webhook is sent.
The request includes these headers:
| Header | Meaning |
|---|---|
X-Orderly-Event | order.dispatched or shipment.updated |
X-Orderly-Delivery | Stable delivery ID reused when the same batch is retried |
X-Orderly-Timestamp | Unix timestamp used in the signature |
X-Orderly-Signature | v1= followed by a hexadecimal HMAC-SHA256 digest |
{
"id": "d9ec1693-3b18-4512-a626-2a1d94a280e8",
"event": "order.dispatched",
"createdAt": "2026-08-13T10:30:00.000Z",
"organizationId": "a2b4f187-7cf0-4f88-8851-f226362b822e",
"bridgeId": "8a128fa2-47d7-4383-8c8d-5d1d36e9eb16",
"data": {
"orders": []
}
}
Verify the signature
Calculate HMAC-SHA256 over the exact string
<X-Orderly-Timestamp>.<raw request body> using the configured secret. Compare
the lowercase hexadecimal result with the value after v1= using a constant-
time comparison.
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifyOrderlyWebhook(
rawBody: Buffer,
timestamp: string,
signatureHeader: string,
secret: string
) {
const supplied = Buffer.from(signatureHeader.replace(/^v1=/, ''), 'hex')
const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest()
return supplied.length === expected.length && timingSafeEqual(supplied, expected)
}
Reject old timestamps and store delivery IDs long enough to prevent replay.
Return the same successful acknowledgement when an already accepted delivery ID
is received again.
Return a 2xx response only after accepting the batch. Orderly retries 429,
network, timeout, and 5xx failures with backoff. Other 4xx responses are
treated as permanent delivery failures.
When Custom API 2 reports a shipment, its API key must include
shipments:write, and the supplied orderId must belong to the same
organization. The shipment's bridgeId should identify API 2; Orderly still
routes the later shipment.updated return by the related order's source bridge.
Destination security
Orderly accepts only public HTTPS destinations. Localhost, URL credentials, private IPv4 ranges, and local/private IPv6 ranges are rejected. The signing secret is masked after saving and is never returned to the browser.