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 is empty, and Delivery Details shows why the attempt couldn't be made.
If you return an error, include a short message in the response body. The delivery log shows the start of it in 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, because BillPro couldn't tell what the status was before. 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. The gaps between retries grow from 30 seconds to one week. Retrying lasts about 11 days in total.
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 user icon menu, select Webhooks.
- Click the endpoint URL.
- Scroll to 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. |
Click a Delivery ID to open Delivery Details. It shows the timestamps, the last response code, the start of your endpoint's response, and the Signed payload. The signed payload is the exact JSON document that BillPro sent.
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 11-day schedule.
The previous Response is replaced by the new attempt's result.
Send again isn't available 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 | 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. |
| "Endpoint is disabled" when you send a test or a delivery again | 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 menu | Your user isn't an Admin, or doesn't have API Access | Ask an Admin to change your role. Contact BillPro to turn on API Access. |
Updated about 11 hours ago
Recommended reading
Learn what each event contains