---
updatedAt: 2026-06-15T23:40:14.000Z
---

Fetch the complete documentation index at: https://developer.bill.com/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Webhook API general rules

The BILL webhook API follows a set of rules.

## Access rules

<Table align={["left","left"]}>
  <thead>
    <tr>
      <th>
        Design
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        **Sandbox base URL**
      </td>

      <td>
        `https://gateway.stage.bill.com/connect-events`
      </td>
    </tr>

    <tr>
      <td>
        **Production base URL**
      </td>

      <td>
        `https://gateway.prod.bill.com/connect-events`
      </td>
    </tr>

    <tr>
      <td>
        **Security**
      </td>

      <td>
        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.

        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 <Anchor label="Test with BILL webhook security" target="_blank" href="doc:test-with-webhook-security">Test with BILL webhook security</Anchor> for more information.
      </td>
    </tr>

    <tr>
      <td>
        **Subscription limits**
      </td>

      <td>
        You can create up to 10 webhook subscriptions per organization. In addition, BILL does not allow duplicate subscriptions for the same set of events and same notification URL.
      </td>
    </tr>
  </tbody>
</Table>

## Authentication rules

Both organization-level users and partner users can set up webhooks. Simply set the required header values in your webhook API requests and you have access to webhooks at the desired level.

| Organization-level header values for webhooks                                   | Partner-level header values for webhooks                                        |
| :------------------------------------------------------------------------------ | :------------------------------------------------------------------------------ |
| `sessionId`: API session ID generated with `POST /v3/login`                     | `sessionId`: Partner API session ID generated with `POST /v3/partner/login`     |
| `devKey`: Developer key sent to you by BILL when you create a developer account | `appKey`: Application key sent to you by BILL when you create a partner account |

| Header value for setting up subscriptions for Spend & Expense events |
| :------------------------------------------------------------------- |
| `apiToken`: Access token for Spend & Expense API authentication      |

## Idempotent key rules

An API request is idempotent when the response shows the same result each time the same request is made.

<Table align={["left","left"]}>
  <thead>
    <tr>
      <th>
        Design
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        **Endpoints that require an idempotent key**
      </td>

      <td>
        Only two BILL webhook endpoints require an idempotent key.

        * **Create a subscription**: `POST /v3/subscriptions`
        * **Create a security key**: `POST /v3/subscriptions/{subscriptionId}/security-key`This ensures that you do not accidentally create multiple subscriptions or security keys for the same set of events.
      </td>
    </tr>

    <tr>
      <td>
        **`X-Idempotent-Key`header value**
      </td>

      <td>
        The idempotent key is set as an additional `X-Idempotent-Key` header value. You must generate a new idempotent key for each new request.
      </td>
    </tr>

    <tr>
      <td>
        **Time To Live (TTL)**
      </td>

      <td>
        For an idempotent key used in an API request, the API response is cached for 24 hours.

        For this duration, if your API request results in a successful response, and then you make another API request with the same `X-Idempotent-Key` header value, you will receive the cached response from your previous API request.

        If your API request results in an error, and then you use the same `X-Idempotent-Key` header value with your fixed API request, you will continue to receive the cached error response from your previous API request. In this case, you must send a new `X-Idempotent-Key` header value with your fixed API request for a successful response.
      </td>
    </tr>

    <tr>
      <td>
        **UUID4 format**
      </td>

      <td>
        All your idempotent keys must be in the UUID4 format.

        UUID4 is a randomly-generated 128-bit identifier. You can easily generate a value for your integration. For example, in Python, you can generate a UUID4 value with `uuid.uuid4()`.
      </td>
    </tr>

    <tr>
      <td>
        **Errors**
      </td>

      <td>
        Incorrect usage of the idempotent key results in errors. See the next section for more information about idempotent key errors. When you receive an idempotent key error, fix the problem at your end and retry.
      </td>
    </tr>
  </tbody>
</Table>

## Idempotent key errors

Incorrect usage of the idempotent key results in errors.

| Error                                                                                         | HTTP status           |
| :-------------------------------------------------------------------------------------------- | :-------------------- |
| Missing idempotent key                                                                        | 400 Bad Request       |
| Malformed idempotent key                                                                      | 400 Bad Request       |
| The same idempotent key is used for different operations                                      | 409 Conflict          |
| Additional requests are made with the same idempotent key when another request is in progress | 429 Too Many Requests |

See <Anchor label="Webhook API error responses" target="_blank" href="doc:webhook-api-error-responses">Webhook API error responses</Anchor> for information about the complete list of webhook API error responses.