Receive order webhooks
Subscribe a URL to order events, answer the activation ping, protect the endpoint with basic auth and handle deliveries and failures.
Goal: your server learns when an order is created, updated, closed or shipped, without polling. You need a public HTTPS endpoint and a token with hub.write plus read access to the topic’s resource (orders.read for order topics).
1. Answer the activation ping
When you subscribe, Bolder first sends one POST to your URL with an X-Hook-Ping header and a body whose topic is activation. The subscription only becomes active if your endpoint answers 2xx with the same value in an X-Hook-Pong header. Deploy the handler before you subscribe:
import express from 'express'
const app = express()
app.post('/webhooks/bolder', express.json(), (req, res) => {
const ping = req.get('X-Hook-Ping')
if (ping) return res.set('X-Hook-Pong', ping).sendStatus(200)
const event = req.body
console.log(event.topic, event.item_id)
res.sendStatus(200)
})
app.listen(3000)
2. Subscribe
import { createBolder } from 'bolder-api-client'
const bolder = await createBolder({ token: process.env.BOLDER_TOKEN, shopId: 1234 })
const hook = await bolder.webhooks.create({
topic: 'order',
url: 'https://myapp.com/webhooks/bolder',
auth: { type: 'basic', username: 'bolder', password: process.env.WEBHOOK_PASSWORD }
})
This calls POST /v2/webhooks?shop_id=1234; POST /v2/sellers/{seller_id}/webhooks does the same. Subscriptions belong to the seller (the account), not to a single shop.
By default a subscription doesn’t receive events caused by the app that created it (an order your integration created through the API, for example). Pass notify_origin: true to receive those too.
Topics are hierarchical: order receives every order event, order.updated receives all its subtopics (order.updated.closed, order.updated.shipped, …), and order.created only that one. The full list of topics is in the Webhooks reference.
3. Verify deliveries
Deliveries are not signed. To make sure a request comes from Bolder, subscribe with auth.type: basic (as above): every delivery, the activation ping included, then carries those credentials in the Authorization header. Check them before anything else (register this middleware before the route):
const expected = 'Basic ' + Buffer.from(`bolder:${process.env.WEBHOOK_PASSWORD}`).toString('base64')
app.use('/webhooks/bolder', (req, res, next) => {
if (req.get('Authorization') !== expected) return res.sendStatus(401)
next()
})
Use HTTPS so the credentials are not sent in clear text.
4. Handle the payload
Each delivery is a JSON POST with the event:
| Field | |
|---|---|
topic |
The event topic, singular (order.updated.closed). |
item_type, item_id |
The record the event is about (order, 123). |
changes |
Changed fields as {field: [old, new]}. |
payload |
Extra data for the event, if any. |
created_on |
When the event happened. |
shop_id, shop_subdomain |
The shop it happened in. |
_embedded.item |
The record itself, as the v2 API returns it. |
Answer 2xx quickly (requests time out after 30 seconds) and do slow work in a background job. Deliveries can arrive more than once and out of order: use item_id and created_on to keep your records consistent, or fetch the order again with GET /v2/orders/{id}.
Failures
- A failed delivery is retried up to 10 times over about 24 hours.
- Answering
410 Gonedisables the subscription at once. - After 100 failed attempts in a row (retries included) the subscription is marked as failed and stops receiving events. Timeouts and
502,512and523errors don’t count towards that limit. - When a subscription stops, a
webhook.disabledevent is sent and the merchant gets an alert. - Fix the endpoint, then call
PUT /v2/webhooks/{id}/reactivatefor a failed subscription. One disabled by a410has to be subscribed again. GET /v2/webhooks/{id}/historylists the recent events of the subscription’s topic.
Next
- Webhooks reference: every topic and what triggers it.
- Create an order and take payment