Handle webhook deliveries
Acknowledge fast, handle repeats and out-of-order events, and fix failed deliveries
Build your endpoint to accept each delivery quickly, and to cope with repeats and events that arrive out of order. When something goes wrong, use the delivery log to see what happened and send the event again.
Before you start
- An endpoint that verifies signatures. See Verify webhook signatures.
- Somewhere to store the delivery IDs you have processed, such as a database table with a unique key.
Receive a delivery
- Verify the signature. If it fails, return
401and stop. - Read the
X-BillPro-Deliveryheader. If you have already processed this delivery ID, return200and stop. - Save the event, or put it on a queue.
- Return a
2xxstatus code straight away. - Process the event in the background. Record the delivery ID when processing succeeds.
Keep step 4 fast. Don't call other systems or run slow work before you reply. A slow reply can count as a failed attempt, and BillPro sends the event again.
What counts as success
BillPro counts a delivery as Succeeded when your endpoint accepts it with a 2xx status code.
Anything else counts as a failed attempt. This includes other status codes, and attempts where your server can't be reached or doesn't answer. For those, Response shows a dash, and Delivery Details shows why the attempt couldn't be made.
If you return an error, include a short message in the response body. Delivery Details shows the start of it under Response, which helps you debug.
Handle repeats
Delivery is at least once. You can receive the same event more than once, for example when your reply was lost or you clicked Send again.
The X-BillPro-Delivery header holds the delivery ID. It stays the same on every retry and every redelivery of the event. The delivery log shows the same value as Delivery ID.
To make your handler safe to repeat:
- Store each delivery ID once processing succeeds, with a unique constraint.
- Before you process an event, check whether its delivery ID is already stored. If it is, reply
200and skip it. - Record the ID only after processing succeeds. If processing fails, you can then take the event again from a retry or a redelivery.
Handle ordering
BillPro doesn't guarantee the order of deliveries. An order can reach you as Active before you receive Pending, especially after a retry.
- Use the
occurred_atfield in the payload to decide which event is newest for an order. It's the time the order reached the status. Don't apply an event older than the one you already have. - Some events have
previous_statusset tonull, such asorder.draftfor a new order. Truststatusandoccurred_atfor these. - If you need the current state, fetch the order with the API after you receive the event. The
links.orderURL in the payload points to it. See Show order.
Retries
If an attempt fails, BillPro retries the delivery automatically on a fixed schedule. The delivery shows Retrying, and Next attempt shows when BillPro will try again.
Each delivery gets up to 12 attempts: the first one, then 11 retries. Retrying lasts about 12 days in total.
| Retry | Wait after the failed attempt |
|---|---|
| 1 | 30 seconds |
| 2 | 2 minutes |
| 3 | 10 minutes |
| 4 | 30 minutes |
| 5 | 1 hour |
| 6 to 9 | About 2 days in total |
| 10 | 3 days |
| 11 | 7 days |
When all 12 attempts have failed, the delivery shows Failed. BillPro stops trying. You can still send it again by hand.
If a delivery fails its whole retry schedule and nothing sent to the endpoint succeeds in that time, BillPro switches the endpoint off. Its status becomes Auto-disabled, the endpoint page shows the reason, and BillPro emails the merchant. BillPro sends nothing to it until you turn it back on.
Consecutive failures on the endpoint page is for information only. It doesn't decide when an endpoint is switched off.
To turn an auto-disabled endpoint back on:
- Fix the problem on your server.
- Open the endpoint, click Edit, set the status to
Active, and click Save. - Send the
FailedandCanceleddeliveries you still need again. BillPro doesn't resume them by itself. See Send a delivery again.
Delivery statuses
| Status | Meaning |
|---|---|
Pending | Queued for its first attempt, or for a redelivery. |
Retrying | At least one attempt failed. BillPro will try again at Next attempt. |
Succeeded | Your endpoint accepted it with a 2xx code. |
Failed | All 12 attempts failed. BillPro has stopped trying. |
Canceled | The endpoint was switched off before the delivery was sent. |
Use the delivery log
Each endpoint page has a Recent deliveries table, newest first, 20 to a page.
- From the profile menu, select Webhooks.
- Click the endpoint URL.
- Scroll to Recent deliveries.

Recent deliveries
| Column | Shows |
|---|---|
| UID | The order ID. Click it to open the order. A dash for a test event, or if the order was deleted. |
| Event | The order event, such as Active. |
| Delivery ID | The value of the X-BillPro-Delivery header. |
| Created | When the delivery was created. |
| Status | The delivery status. |
| Attempts | How many times BillPro has tried to send it. |
| Response | The last status code your endpoint returned. |
| Next attempt | When BillPro will try again, if it is retrying. |
Times are in UTC.
Click a Delivery ID to open Delivery Details. It shows when the event occurred, the last and next attempt, the Response code, the start of your endpoint's reply under Response, and the Signed payload. The signed payload is the document BillPro sent, laid out for reading. You can't use it to check a signature.

Delivery details
The endpoint page also shows Last success, Last failure and Consecutive failures for the endpoint. Consecutive failures counts failed attempts in a row, not deliveries, and goes back to zero after the next success. The Webhooks list shows Last Success and Last Failure for each endpoint.
The log is for checking recent problems, not a permanent archive. BillPro removes finished deliveries (Succeeded, Failed and Canceled) on a schedule. Keep your own records, keyed on the delivery ID, if you need a permanent history.
Send a delivery again
- Open the endpoint and find the delivery in Recent deliveries.
- Click Send again on the row.
BillPro sends the same document again, with the same delivery ID. Only the signature changes, because BillPro signs it again with a new timestamp. If you already processed that ID, your duplicate check will skip it.
- For a
Succeeded,FailedorCanceleddelivery, BillPro resets it toPending. The attempt count goes back to zero, and it gets a new retry schedule. - For a
Retryingdelivery, BillPro tries again straight away. It keeps its attempt count, so clicking Send again doesn't restart the 12-day schedule.
The previous Response is replaced by the new attempt's result.
Send again doesn't appear when:
- the delivery is
Pending, because it's already queued - the endpoint is
InactiveorAuto-disabled. Turn it on first.
Troubleshooting
| What you see | Likely cause | What to do |
|---|---|---|
Response is 401 on every delivery | The signature check fails | Use the raw body, not parsed JSON. Use the full secret with the whsec_ prefix. Check that you're using the secret for this endpoint. |
401 on every delivery after a rotation | Your server still has the old secret | Update your server with the new secret. Then send the failed deliveries again. See Rotate a webhook signing secret. |
Deliveries stay Retrying with no Response | BillPro can't reach your server | Open Delivery Details to see why the attempt couldn't be made. Check that the URL is public, uses https:// with a valid certificate, and that your firewall allows the requests. |
Response is 404 or 405 | The path is wrong or doesn't accept POST | Check the Endpoint URL and your route. |
Response is 5xx | Your code threw an error | Check your server logs. Reply before you do slow work. |
The endpoint shows Auto-disabled | Deliveries kept failing | The reason shows under the status on the endpoint page. Fix the cause, set the status to Active, and send the failed deliveries again. |
| Send test event is greyed out | The endpoint is switched off | Set the status to Active. |
| Can't send more test events | You sent 10 to this endpoint in the last hour | Wait, or use Send again on an earlier test. |
| You processed the same event twice | Repeats aren't handled | Skip delivery IDs you've already processed. |
| The address is refused when you save | It's http://, private, internal or has credentials | Use a public https:// URL without a username or password. |
| Webhooks isn't in the profile menu | Your user doesn't have the admin_user role, or doesn't have API Access | Ask an admin to change your role. Contact BillPro to turn on API Access. |
Updated 14 days ago
Recommended reading
Learn what each event contains