Skip to main content

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

  1. Open Bridges → Add Bridge.
  2. Select Custom API under Custom.
  3. Give it a recognizable display name, such as "Warehouse API".
  4. 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.
  5. 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.dispatched sends orders routed to this bridge by a dispatcher.
  • shipment.updated sends 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:

FieldMeaning
updatedSinceOptional ISO timestamp; overrides the rolling cursor
lookbackMinutesFirst-run lookback, default 60 and maximum 10,080
statusOptional shipment status filter
limitMaximum 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:

HeaderMeaning
X-Orderly-Eventorder.dispatched or shipment.updated
X-Orderly-DeliveryStable delivery ID reused when the same batch is retried
X-Orderly-TimestampUnix timestamp used in the signature
X-Orderly-Signaturev1= 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.

Next steps