# Receive webhooks

> Register webhook endpoints, understand multi-endpoint vs legacy mode, and use the delivery log and resend.

Source: https://help.boltapp.com/developers/webhooks

Bolt webhooks notify your server when checkout events happen. After you register an endpoint, Bolt sends POST requests when you subscribe to those events.

Event types and payloads are documented in [Bolt Webhooks](/api-reference/guides/webhooks).

## Webhook use cases [#webhook-use-cases]

Common reasons to use webhooks:

* Notifying your store when a transaction is approved or rejected by Bolt
* Sending your store the `transaction_id` for back-office operations
* Sending transaction details such as credit card metadata

## Set up webhooks [#set-up-webhooks]

Bolt offers two methods for configuring webhook subscriptions: the API and the merchant dashboard.

### Manage webhooks over the API [#manage-webhooks-over-the-api]

Use the `/v1/webhooks` endpoints to manage subscriptions from code. The example environment variables are placeholders you set in your application: `BOLT_API_KEY` and `BOLT_DIVISION_PUBLIC_ID`. All calls authenticate with your API key; `division_id` is your **Division Public ID** from the **Identifiers** card of the API page. Schemas are under the Webhooks tag of the [Bolt API OpenAPI spec](https://assets.boltapp.com/external-api-references/bolt.yml).

<CodeGroup>
  <CodeBlockTabs defaultValue="TypeScript">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="TypeScript">
        TypeScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="JavaScript">
        JavaScript
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="cURL">
        cURL
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="TypeScript">
      ```typescript
      const response = await fetch("https://api-sandbox.boltapp.com/v1/webhooks", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-API-Key": process.env.BOLT_API_KEY,
        },
        body: JSON.stringify({
          division_id: process.env.BOLT_DIVISION_PUBLIC_ID,
          url: "https://example.com/bolt/webhooks",
          event_group: "all",
        }),
      });
      const { webhook_id } = await response.json();
      ```
    </CodeBlockTab>

    <CodeBlockTab value="JavaScript">
      ```javascript
      const response = await fetch("https://api-sandbox.boltapp.com/v1/webhooks", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-API-Key": process.env.BOLT_API_KEY,
        },
        body: JSON.stringify({
          division_id: process.env.BOLT_DIVISION_PUBLIC_ID,
          url: "https://example.com/bolt/webhooks",
          event_group: "all",
        }),
      });
      const { webhook_id } = await response.json();
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python">
      ```python
      import os
      import requests

      response = requests.post(
          "https://api-sandbox.boltapp.com/v1/webhooks",
          headers={
              "Content-Type": "application/json",
              "X-API-Key": os.environ["BOLT_API_KEY"],
          },
          json={
              "division_id": os.environ["BOLT_DIVISION_PUBLIC_ID"],
              "url": "https://example.com/bolt/webhooks",
              "event_group": "all",
          },
      )
      webhook_id = response.json()["webhook_id"]
      ```
    </CodeBlockTab>

    <CodeBlockTab value="cURL">
      ```bash
      curl -X POST 'https://api-sandbox.boltapp.com/v1/webhooks' \
        -H 'Content-Type: application/json' \
        -H 'X-API-Key: YOUR_API_KEY' \
        -d '{
          "division_id": "YOUR_DIVISION_PUBLIC_ID",
          "url": "https://example.com/bolt/webhooks",
          "event_group": "all"
        }'
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

Expected result: HTTP 200 with a `webhook_id`. Send exactly one of `event_group: "all"` or an `events` array naming specific events: sending both, or neither, returns HTTP 400.

The other operations:

* `GET /v1/webhooks?division_id=...` lists the division's webhooks. The response contains a `webhooks` array.
* `GET /v1/webhooks/{webhook_id}` retrieves one webhook by its ID.
* `DELETE /v1/webhooks/{webhook_id}` removes one and returns HTTP 200 with no body.

There is no update operation over REST. To replace an endpoint, create the new webhook, verify delivery, then delete the old one.

Responses worth recognizing:

* **HTTP 422** on create: an existing webhook would already deliver the same events to that URL. List the division's webhooks and delete the duplicate first.
* **HTTP 400** naming a configuration count limit: a division holds at most ten webhooks. Delete one before adding another.
* **HTTP 400** saying the division must have the WebhooksV2 feature enabled: the division is on the legacy single-URL mode described [below](#multiple-endpoints-vs-legacy-single-url).

### Merchant Dashboard [#merchant-dashboard]

1. Log in to your Bolt [Merchant Dashboard](https://merchant.boltapp.com).
2. Navigate to **Administration**.
3. Under **Developers**, select **Webhooks**.
4. In the **Endpoints** card, select **Add endpoint** and enter your HTTPS URL. Expected result: the endpoint appears in the table.
5. [Verify the authenticity of requests](/api-reference/guides/verify-webhooks) to confirm they originated from Bolt.

The dashboard **Webhooks** page needs a role with write access to webhook settings. Without it, the page does not appear under **Developers**; ask a dashboard administrator to grant the permission or to make the change.

If the response to a webhook request is not **HTTP 200 OK** or the response body is not valid JSON, Bolt retries with exponential back-off.

## Verify delivery in sandbox [#verify-delivery-in-sandbox]

1. Register a sandbox endpoint using the API or dashboard.
2. Complete a sandbox order that produces a subscribed event.
3. Inspect **Webhook Events** and your server logs.

Expected result: the endpoint receives the event and validates its signature. If delivery fails, correct the handler or connectivity problem and use the [delivery log](#delivery-log-and-resend) to inspect or resend the event.

## Multiple endpoints vs legacy single URL [#multiple-endpoints-vs-legacy-single-url]

Divisions run in one of two webhook modes:

* **Multi-endpoint (V2)**: the division supports up to ten endpoints, each managed independently in the dashboard and over `/v1/webhooks`. Confirm that multi-endpoint webhooks are enabled for your division.
* **Legacy single URL**: older divisions store exactly one webhook URL. The dashboard shows it as a single row in the same **Endpoints** table, so the page looks identical until you try to add a second one: **Add endpoint** is then disabled with the tooltip "This division supports one webhook endpoint. To send events somewhere else, edit the endpoint listed below." The `/v1/webhooks` create call on such a division returns HTTP 400 saying the WebhooksV2 feature must be enabled.

If your division is on the legacy mode and you need more than one endpoint, or events are not arriving at an endpoint you registered, [contact support](/support) and ask to have multi-endpoint webhooks enabled for the division. Which mode a division gets by default depends on its platform and when it was created, so two of your own divisions can differ.

### Replace an endpoint safely [#replace-an-endpoint-safely]

* **Working only over REST**, where there is no update operation: add the new endpoint, confirm deliveries in the event log, then remove the old one.
* Endpoints are independent: removing one never affects the others. Confirm what you are deleting before removing it, especially if teammates or other systems registered endpoints on the same division.
* If a change is refused as a duplicate, another endpoint on the division already points at that URL. List the endpoints and decide which one you meant, rather than deleting one to make room.

## Delivery log and resend [#delivery-log-and-resend]

The Webhooks page includes a **Webhook Events** section showing recent deliveries for the division:

* Filter by status: **All**, **Success**, **Failure**, or **Ignore**.
* Search by transaction reference to trace one order end to end.
* Each event shows its delivery attempts and, on failure, the reason.
* **Resend** replays a delivery to your endpoint after you fix a bug or an outage on your side. **Invalidate** marks an event as one you do not want delivered, with a reason.

<Note>
  If every delivery fails and your endpoint looks healthy from your side, check whether your infrastructure filters inbound traffic. A WAF, an IP allowlist, or a private ingress blocks Bolt before your code runs, which from Bolt's side is indistinguishable from your endpoint being down. Ask Bolt support for the current egress IP ranges and allowlist them.
</Note>

## Related [#related]

* [Webhook events reference](/api-reference/guides/webhooks)
* [Verify webhooks](/api-reference/guides/verify-webhooks)
* [Review webhook failures](/api-reference/guides/webhook-failures)
* [API keys](/getting-started/api-keys)
