Article
Webhooks: react in your own system when a purchase order is received

When a delivery against a purchase order has been fully received in OpenBoxes, something often needs to happen somewhere else: the finance system marks the supplier's invoice as ready to check, a request in another tool is closed, or someone is told the goods are in. Instead of checking OpenBoxes for that on a timer, you can have OpenBoxes Lift tell your system when it happens, with a signed webhook. The event is called order.received.
This article walks through what that event means, what it carries, how to build a receiver that handles it safely, and where webhooks stop being the right tool. The reference is in the docs: Webhooks.
Every plan. The OpenBoxes events (orders, shipments and stock adjustments) are delivered on every plan: Shared, Dedicated and Enterprise.
What order.received means#
order.received is sent when a purchase order in your OpenBoxes instance is fully received: everything ordered has been received, or cancelled when it was received. A partial receipt does not trigger it, and usually sends no event at all. When some lines are in and others are not, OpenBoxes shows the order as Partially received.
Three details shape how you use it:
- It arrives within a couple of minutes, not instantly. We look at your instance for changes and then deliver what we find.
- It reports the state an order reached, not every step on the way. If an order moves through several statuses between our checks, you get the last one. An order placed and fully received in quick succession can arrive as
order.receivedwith noorder.updatedbefore it. - It says an order was received, not what was received. The event carries the order's identity and status, not its lines or quantities. For those, read the order afterwards (below).
The OpenBoxes events available as webhooks today are orders (created, approved, received, cancelled, updated), shipments (created, shipped) and stock adjustments. The event list on the Webhooks page in the portal is the authoritative list, and it is the same on every plan. Stock-level changes, low-stock alerts and expiry warnings are not webhook events; the daily stock alerts cover them instead: below-minimum and nearing-expiry by email, and stock-outs also to Slack, Microsoft Teams or PagerDuty.
What a delivery carries#
Every delivery is an HTTP POST with the same JSON envelope:
id: the event's id, stable across retries. You will use it to spot duplicates.type: here,order.received.created: when we sent it, as a Unix timestamp in seconds.data: the event's own fields.
For order.received, data holds tenantId, orderId, orderNumber, orderType, status (RECEIVED) and occurredAt, and also approvedAt and completedAt when OpenBoxes has those dates for the order.
You do not need a real receipt to see the exact shape. With an API key that has the admin scope (created from the portal's API tab, so it is bound to your account), ask for the sample envelope:
curl https://api.openboxes.cloud/api/v1/webhooks/event-types/order.received/sample-envelope \
-H "X-API-Key: $OB_API_KEY"
It returns the whole envelope a real delivery would send, with fixed, obviously synthetic values, so you can keep it as a test fixture.
Setting it up#
- In the portal, open Webhooks in the left navigation.
- Add a webhook: your endpoint's HTTPS address, a name, and the events. For this job,
order.received. - Copy the signing secret (it starts
whsec_). It is shown once. If you lose it, regenerate it and update your receiver.
The address must be public HTTPS. Private and internal network addresses are refused, and redirects are not followed.
No endpoint yet? Create a test receiver on the same page. It gives you an address to register against, and the requests it captures appear in the portal, signed exactly like real ones. Test receivers last 24 hours. You can also send a signed sample of any event type to a real endpoint at any time.
The Shared plan allows 10 webhook endpoints and 1,000 deliveries an hour across all of them. Deliveries past the hourly cap are dropped, not queued.
A receiver that holds up#
1. Verify the signature before anything else#
Each delivery carries two headers: X-OpenBoxes-Timestamp and X-OpenBoxes-Signature. To check them:
- Take the timestamp header, a single
., and the raw request body, in that order. - Compute an HMAC-SHA256 of that string with your
whsec_secret, and hex-encode it. - Compare it with the signature header after its
v1=prefix, using a constant-time comparison. - Reject a request whose timestamp is more than 5 minutes from your clock.
The usual causes of "signature does not match" are three mistakes: comparing against the whole header including v1=, signing the body without the timestamp and dot, or parsing the JSON and re-encoding it before verifying. Sign the exact bytes you received.
2. Answer quickly, then do the work#
Return a 2xx as soon as the signature checks out and the event is stored, and do the real work in a background job. If your endpoint is slow or fails, we retry after about 1 minute, then 5 minutes, 15 minutes, 1 hour and 4 hours. After that the delivery is marked exhausted, and you can retry it by hand from the delivery log on the Webhooks page.
3. Expect the same event twice#
Delivery is at least once. A retry, or a crash at the wrong moment, can deliver the same event again. Record each envelope id you have processed and skip one you have already seen. Otherwise a retried order.received could mark the same invoice twice.
4. Check what kind of order it is#
data.orderType says which kind of OpenBoxes order this is; the sample shows PURCHASE_ORDER. If your job is only about purchase orders, check it before acting.
5. Read the order for the details#
To see what arrived, read the order through the Purchasing API on your instance's address, which returns the order with its lines and each line's quantityReceived (the order's own status in that response can still read PLACED: OpenBoxes works out Received from the quantities, so read the lines):
curl -H "X-API-Key: $OB_API_KEY" \
"https://acme.openboxes.cloud/api/v1/purchaseOrders/<orderId>"
Replace acme with your subdomain. See Authentication for keys, and Your first OpenBoxes API call if you have not made one yet.
Where webhooks stop#
Webhooks are a notification channel, not a ledger. A few cases will not produce an event:
- Deliveries past your hourly cap are dropped, and a dropped event leaves nothing in the delivery log.
- Several changes to one order between our checks collapse into the last one.
- Something deleted outright in OpenBoxes leaves nothing for us to see.
- A purchase order completed by a partial receipt of a shipment that is never fully received (for example a shipment that also carries another order's lines), or one that counts as received only because its remaining line was cancelled on the order afterwards, is not announced.
So if completeness matters, for example every received order must reach your finance system, also reconcile on a schedule: once a day, list the orders with the RECEIVED status through the Purchasing API and compare them with what your system has processed. The list returns orders for the key's location (its creator's default location in OpenBoxes), so if you receive at several locations, reconcile per location, or read each order your system expected by its id. The webhook makes you fast; the reconciliation makes you complete.
Purchase orders themselves are created, placed and received in the OpenBoxes web application, not through the API. The API reads them; the webhook tells you when to read.
Summary#
order.receivedfires when a purchase order is fully received, within a couple of minutes; a partial receipt does not trigger it.- OpenBoxes events are delivered on every plan: Shared, Dedicated and Enterprise.
- Verify the signature on the raw body, answer
2xxfast, and deduplicate on the envelopeid. - The event carries the order's identity, not its lines: read the order through the Purchasing API, and reconcile daily if every order must be counted.
All the ways OpenBoxes Lift connects to other tools are on Integrations.
Keep reading
- GuideWebhooks
- GuidePurchasing
- GuideAuthentication
- On the siteIntegrations in OpenBoxes Lift
- What's newWhat shipped in October 2026
