Webhook events and payloads

The request BillPro sends, the event document and an example payload

Each webhook is an HTTPS POST request with a JSON body. The body describes one order event. This page shows the request and the fields you can rely on.


The request

POST /billpro/webhooks HTTP/1.1
Host: erp.example.com
Content-Type: application/json
X-BillPro-Signature: t=1768478400,v1=<64 lowercase hex characters>
X-BillPro-Delivery: 3f2b1c88-9d4e-4a7b-9f01-2c6d5e8a1b34

{ ...event document... }
HeaderValue
Content-Typeapplication/json
X-BillPro-SignatureThe timestamp and signature. See Verify webhook signatures.
X-BillPro-DeliveryThe delivery ID. The same on every retry and redelivery. Use it to skip repeats.

Treat the delivery ID as an opaque string. Don't rely on its format.


The event document

This is a real order.active document. The order in it is BillPro's sample order, so its details are made up.

{
  "id": "3f2b1c88-9d4e-4a7b-9f01-2c6d5e8a1b34",
  "event": "order.active",
  "occurred_at": "2026-01-15T12:00:00.000Z",
  "data": {
    "previous_status": "pending",
    "status": "active",
    "order": {
      "id": 1001,
      "merchant_invoice_id": "SAMPLE-0001",
      "order_type": {
        "value": "single_payment",
        "label": "Single payment"
      },
      "status": {
        "value": "active",
        "label": "Active"
      },
      "currency": "GBP",
      "channel": "ECOM",
      "description": "Sample order for a webhook test ping",
      "collect_initial_payment": true,
      "card_details_saved": "No",
      "initial_amount": "50.00",
      "customer_id": 2001,
      "customer": {
        "display_customer_id": "SAMPLE-CUST-0001",
        "merchant_customer_id": "SAMPLE-CUST-0001",
        "first_name": "Sample",
        "last_name": "Customer",
        "full_name": "Sample Customer",
        "email": "[email protected]",
        "phone": "+44 20 7946 0000",
        "date_of_birth": null,
        "street": "1 Sample Street",
        "city": "London",
        "state": null,
        "postal_code": "EC1A 1BB",
        "country": "GB",
        "country_name": "United Kingdom"
      },
      "order_products": [
        {
          "id": 3001,
          "product_id": 4001,
          "name": "Sample product",
          "selection_text": null,
          "options": null,
          "qty": 1,
          "amount": "50.00",
          "products_total": "50.00",
          "discount_amount": "0.00",
          "fee_amount": "0.00",
          "tax_amount": "0.00",
          "subtotal_amount": "50.00",
          "product_discount_id": null,
          "product_fee_id": null,
          "product_tax_id": null
        }
      ],
      "summary": {
        "products_total": "50.00",
        "fee_total": "0.00",
        "discount_total": "0.00",
        "tax_total": "0.00",
        "tax_type": null,
        "sub_total": "50.00",
        "initial_amount": "50.00",
        "payment_amount": "50.00"
      },
      "tag_list": [],
      "created_at": "2026-01-15T12:00:00.000Z",
      "updated_at": "2026-01-15T12:00:00.000Z",
      "status_updated_at": "2026-01-15T12:00:00.000Z"
    }
  },
  "links": {
    "order": "https://app.bill.pro/api/v2/profiles/7/orders/1001",
    "payments": "https://app.bill.pro/api/v2/profiles/7/orders/1001/payments",
    "scheduled_payments": "https://app.bill.pro/api/v2/profiles/7/orders/1001/scheduled_payments"
  }
}
FieldDescription
idThe event ID. It matches the X-BillPro-Delivery header.
eventThe event name, such as order.active.
occurred_atWhen the order reached the new status, in ISO 8601 UTC with milliseconds. Use it to put events in order.
data.previous_statusThe status the order moved from. null on a test event, and on an event where BillPro couldn't tell the previous status.
data.statusThe status the order moved to.
data.orderThe order at the moment of the event. See below.
links.orderAPI URL of the order. Fetch it for the current state.
links.paymentsAPI URL of the payments taken on the order.
links.scheduled_paymentsAPI URL of the order's payment schedule.

data.order holds the order as it was when the event was raised:

  • The order details, such as id, merchant_invoice_id, order_type, status, currency, channel and description.
  • customer: the customer details saved on the order.
  • order_products: every line on the order, with its amounts.
  • summary: the totals for the order.
  • tag_list, and the created_at, updated_at and status_updated_at times.

order_type and status are objects with a value to match on and a label to show. Amounts are strings, such as "50.00".

Key order isn't fixed. Parse the JSON. Don't compare it byte for byte with the example on this page.

To see the full document BillPro sent for any delivery, open the endpoint, click the Delivery ID in Recent deliveries, and look at Signed payload. It's byte for byte what your endpoint received.

📘

Verify before you parse

Check the signature against the raw body first. Parse the JSON only after the signature matches.


Events

EventSent when the order becomes
order.draftDraft: created, nothing collected yet.
order.pendingPending: waiting for the first payment.
order.registeredRegistered: the card is saved and the first payment is waiting.
order.activeActive: collecting payments on its schedule.
order.pausedPaused: paused by the merchant. Payments restart when it's resumed.
order.failedFailed: a payment was declined but can be retried.
order.suspendedSuspended: stopped after a failed payment, on an order that had already collected a payment.
order.rejectedRejected: declined because of a card problem. Retrying won't help.
order.reviewReview: needs a manual check, for example after an overpayment.
order.completeComplete: nothing more is due.
order.cancelledCancelled: cancelled. Nothing more is collected.
order.testNever sent for an order. Sent only when you click Send test event.

The data.status value uses the same names as the order status in the API, such as active.

For how these statuses apply to each order type, see Webhook events and order statuses.


Test events

A test event has the event name order.test. It has the same fields as any other event, and data.previous_status is null.

The delivery isn't linked to an order, but data.order still holds one:

  • Until the endpoint has accepted one delivery, it's a sample order with made-up details, like the one shown above.
  • After that, it's the most recent real order on the profile.

Reply with a 2xx code and take no action, even though the order may be real.


Events added later

BillPro may add new order events. An endpoint with Enable all order events turned on receives them automatically.

Make your handler reply 2xx to events it doesn't recognise, and ignore them. Otherwise BillPro retries them.


Recommended reading

See what each status means for each order type

Did this page help you?