Work with BILL webhooks

When you set up a subscription for your choice of BILL events, you receive real time event notifications when those events are triggered.

❗️

Organization-level users vs Partner users

For organization-level users, BILL sends event notifications only for the BILL organization used to set up a subscription. When a subscription is set up, the organizationId in the API response represents the BILL organization for which the subscription is created.

When a partner user creates a subscription for events, BILL sends event notifications for all organizations created by the partner user.

Subscribe to BILL events

BILL provides a catalog of all the events that you can subscribe to with GET /v3/events/catalog. See the /v3/events/catalog API for more information.

To create a subscription with POST /v3/subscriptions, set the required fields.

FieldDescription
nameSubscription name
statusSubscription status information. Set enabled as true for keeping the subscription enabled. You can use the field to disable the subscription at a later time.

When an event notification fails, BILL tries to send the notification again with exponential backoff. When BILL does not receive a HTTP 200 statusCode after resending a notification even after exponential backoff, BILL sets the subscription enabled status as false and stops sending any notifications.

The enabled status stays as false until the problem is resolved at your end and until the enabled status is set as true again.
eventsIn the events array, add events that you can subscribe to.

- type: Event name
- version: Event versionYou can get the full catalog of all events that you can subscribe to with GET /v3/events/catalog.
notificationUrlThe location where you receive event notifications. The URL that you provide must be HTTPS.

👍

Create a subscription at the Partner level

To create a subscription at the partner level, simply set your partner sessionId (with /v3/partner/login) and appKey as the required header values. The base URL and body parameters in the POST /v3/subscriptions request are the same for partner users and organization users.

Sample request

In this cURL example, a subscription is created for the bill.created, bank.updated, and bill.archived events. The required X-Idempotent-Key, devKey, and sessionId header values are set. A notificationUrl is set as the location URL for you to receive event notifications when any of the subscribed events is triggered.

👍

Use an online service for setting up a notification URL

When you are testing with BILL webhooks, you can use an online service for generating a test notification URL. For example, webhook.site or pipedream.com/requestbin.

curl --request POST \
--url 'https://gateway.stage.bill.com/connect-events/v3/subscriptions' \
--header 'content-type: application/json' \
--header 'X-Idempotent-Key: {UUID4_key}' \
--header 'devKey: {developer_key}' \
--header 'sessionId: {session_id}' \
--data '{
  "name": "Bill management",
  "status": {
    "enabled": true
  },
  "events": [{
    "type": "bill.created",
    "version": "1"
  },
  {
    "type": "bill.updated",
    "version": "1"
  },
  {
    "type": "bill.archived",
    "version": "1"
  }],
  "notificationUrl": "https://example.com/billmanagement"
}'

Response

In the response, a BILL-generated subscription id is available. The organizationId represents the BILL organization for which your subscription is created. BILL sends you event notifications only for the BILL organization used to set up this subscription.

{
  "id": "{subscription_id}",
  "name": "Bill management",
  "organizationId": "{organization_id}",
  "status": {
    "enabled": true,
    "actor": "CLIENT"
  },
  "events": [{
    "type": "bill.created",
    "version": "1"
  },
  {
    "type": "bill.updated",
    "version": "1"
  },
  {
    "type": "bill.archived",
    "version": "1"
  }],
  "notificationUrl": "https://example.com/billmanagement",
  "createdTime": "2025-12-10T23:25:27.127+00:00",
  "securityKey": "{security_key}"
}

When you create a new subscription, a one-time securityKey is one of the generated values in the response. BILL uses the HMAC-SHA256 algorithm to sign all event notifications sent to your notificationUrl with the securityKey. The output format of the generated hash value is base64.

The generated hash is sent as the x-bill-sha-signature header value with all event notifications. With this value, you can verify that each notification sent to you is from BILL.

👍

Security key best practice

Keep the security key for your subscription updated in a timely manner. Generate a new security key for your subscription with POST /v3/subscriptions/{subscriptionId}/security-key.

See the /v3/subscriptions API for more information about the complete list of subscription operations.

Subscribe to Spend & Expense API events

When you are subscribing to the BILL Spend & Expense API events, your POST /v3/subscriptions API request must include the apiToken header value for authentication.

curl --request POST \
--url 'https://gateway.stage.bill.com/connect-events/v3/subscriptions' \
--header 'content-type: application/json' \
--header 'X-Idempotent-Key: {UUID4_key}' \
--header 'apiToken: {api_token}' \
--data '{
  "name": "Bill management",
  "status": {
    "enabled": true
  },
  "events": [{
    "type": "spend.transaction.updated",
    "version": "1"
  }],
  "notificationUrl": "https://example.com/billmanagement"
}'

Generate a test event notification

When you set up a subscription, you can generate a test event with POST /v3/subscriptions/{subscriptionId}/test. Use this endpoint to confirm whether the your subscription and notifications workflow has been successfully set up. BILL sends you a test event notification to the specified notification URL.

In your request, all the fields are optional. To see specific information in the test event notification, you can set eventType, version, and any details. You can get the BILL events catalog with GET /v3/events/catalog.

Sample response

In this response example, a test event notification is available. The JSON-escaped payload is presented as a string. You will receive such a response with you do not set any of the optional values in your POST /v3/subscriptions/{subscriptionId}/test request.

{
    "id": "{event_id}",
    "type": "test",
    "version": "1",
    "subscriptionId": "{subscription_id}",
    "payload": "{\"metadata\":{\"eventId\":\"{event_id}\",
        \"subscriptionId\":\"{subscription_id}\",\"organizationId\":\"{organization_id}\",
        \"eventType\":\"test\",\"version\":\"1\"},
        \"payload\":{\"createdDate\":\"2025-12-15T23:15:27.127+00:00\"}}",
    "createdTime": "2025-12-15T23:15:27.127+00:00",
    "statusCode": 200
}

❗️

Important

All the fields in the test event notification are test values. You cannot use any of the test values for any other operation.

Get events for a subscription

You can get the list of event notifications sent to you for a subscription with GET /v3/events/subscription/{subscription_id}.

👍

Resend a specific event

You can also resend a specific event with POST /v3/events/subscription/{subscriptionId}/event/{eventId}.

Sample response

In this response example, an array of event objects are available along with a BILL-generated event id. Each event object represents the notification that BILL sends to your notificationUrl when the event is triggered. The JSON-escaped payload of each event notification is presented as a string.

[
   {
      "id": "{event_id1}",
      "type": "bill.created",
      "version": "1",
      "subscriptionId": "{subscription_id}",
      "payload": "{\"metadata\": {\"eventId\": \"{event_id1}\",
          \"subscriptionId\": \"{subsrciption_id}\", \"organizationId\": \"{organization_id}\",
          \"eventType\": \"bill.created\", \"version\": \"1\"}, \"bill\": {\"id\": \"{bill_id1}\",
          \"vendorId\": \"{vendor_id}\", \"invoiceNumber\": \"202501\",
          \"invoiceDate\": \"2025-12-16T00:00:00.127+00:00\", \"amount\": 228.99,
          \"createdDate\": \"2025-12-15T23:15:23.127+00:00\", \"archived\": false}}"
      "createdTime": "2025-12-15T23:15:27.127+00:00",
      "statusCode": 200
   },
   {
      "id": "{event_id2}",
      "type": "bill.created",
      "version": "1",
      "subscriptionId": "{subscription_id}",
      "payload": "{\"metadata\": {\"eventId\": \"{event_id2}\",
          \"subscriptionId\": \"{subsrciption_id}\", \"organizationId\": \"{organization_id}\",
          \"eventType\": \"bill.created\", \"version\": \"1\"}, \"bill\": {\"id\": \"{bill_id2}\",
          \"vendorId\": \"{vendor_id}\", \"invoiceNumber\": \"202502\",
          \"invoiceDate\": \"2025-12-16T00:00:00.127+00:00\", \"amount\": 149,
          \"createdDate\": \"2025-12-15T23:17:31.127+00:00\", \"archived\": false}}"
      "createdTime": "2025-12-15T23:17:35.127+00:00",
      "statusCode": 200
   }
]

👍

HTTP 200 statusCode

The HTTP 200 statusCode informs BILL that the event notification was successfully sent to your notificationUrl.

In the next section, we list examples of the BILL webhook notification payloads in the Notification payloads reference.