> For the complete documentation index, see [llms.txt](https://developers.bead.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.bead.xyz/onboarding/partner-webhooks.md).

# Partner Webhooks

Partner webhooks notify your system about activity at the partner level: progress on merchant onboarding applications, settlements that Bead places on hold for merchants under your partner, and reclaim progress on payments for merchants under your partner.

Instead of relying only on repeated polling, your server receives an HTTP `POST` when one of these events occurs.

Bead has two webhook families:

|                  | Payment webhooks                                  | Partner webhooks (this page)                              |
| ---------------- | ------------------------------------------------- | --------------------------------------------------------- |
| Scope            | One terminal                                      | Your whole partner, across every merchant under it        |
| Events           | Payment status changes and reclaim progress       | Application, settlement, and reclaim events               |
| Configure with   | `PUT /Terminals/{id}/webhook`                     | `PUT /merchant-onboarding/{partnerId}/webhook`            |
| Secret           | `signingSecret`, one per terminal, base64 encoded | `webhookSecret`, one per partner, used exactly as issued  |
| Signature header | `X-Webhook-Signature`                             | `X-Webhook-Signature`, plus the legacy `X-Bead-Signature` |
| Retries          | Up to about 24 hours                              | 5 retries over about a minute                             |
| Details          | [Payment Webhooks](/payments/payment-webhooks.md) | This page                                                 |

A partner has one webhook endpoint and one secret. Every partner event type you register is delivered to that single endpoint.

> **Note on naming:** the configuration endpoint path contains `merchant-onboarding` for historical reasons. It configures all partner webhooks, not only application events.

Use partner webhooks when you want to:

* keep your own merchant records in sync with application progress
* trigger internal workflows when an application is signed, returned for changes, or boarded
* learn the reason an application was returned for changes
* learn when a merchant's settlement has been placed on hold
* follow reclaim returns to payers across all of your merchants in one place
* notify internal teams when onboarding or settlement activity requires follow up
* reduce repeated polling against the [Get Status](/onboarding/get-status.md) endpoint
* support operational dashboards and reconciliation workflows

Partner webhooks are configured at the partner level. Once configured, Bead sends events for the applications and merchants associated with that partner.

Partner webhooks are sent only if your partner has an enabled webhook registration that includes the event type. An event type that is not in your registration is not delivered.

### How partner webhooks work

A typical webhook workflow is:

1. You create or manage onboarding applications and merchants under a partner.
2. You configure a partner webhook endpoint.
3. Bead sends an event to your endpoint when a registered event occurs.
4. Your system validates the request, records the event, and updates internal state.
5. If you need the full current state, call the relevant read endpoint. For application events, call `GET /merchant-onboarding/applications/{applicationId}` using the `applicationId` from the webhook. For settlement events, read the settlement using the `settlementId` from the webhook. For reclaim events, read the payment using the `paymentId` from the webhook.

Webhook payloads are event notifications. They are not intended to replace the [Get Status](/onboarding/get-status.md) endpoint, the [Settlements](/settlement/settlements.md) endpoints, or the payment read endpoints as the full source of truth.

### Supported events

Partner webhook configuration supports seven event types in three groups.

#### Application events

| Event type                    | Meaning                                                                                                              | Recommended handling                                                                                                                                               |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `applicationViewed`           | The signer opened the application.                                                                                   | Record the event. No action is required. Call Get Status if you show signer progress.                                                                              |
| `applicationSigned`           | The signer completed the signing step represented by the onboarding package.                                         | Record the event and call Get Status to confirm the current application state.                                                                                     |
| `applicationReviewed`         | A valid event type that is not currently sent. You can include it in your registration, but no events are delivered. | No action. Use Get Status to follow review progress.                                                                                                               |
| `applicationChangesRequested` | Bead returned the application for changes.                                                                           | Store `data.reason`, which describes what needs correcting. Then follow Resolving changesRequested on Get Status and resend the application once the fix is made.  |
| `merchantBoarded`             | The merchant has completed boarding.                                                                                 | Call Get Status and store `onboardedMerchantId` for downstream workflows. The event also carries the merchant, default location, and default terminal identifiers. |

#### Settlement events

| Event type       | Meaning                                                      | Recommended handling                                                                                                                                                                    |
| ---------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `settlementHeld` | A settlement for a merchant under your partner entered hold. | Record the event. Read `data.holdReason` for the reason, then review the merchant's settlements. The event is sent when the settlement enters hold, not again while it remains on hold. |

#### Reclaim events

| Event type             | Meaning                                                                                                                                                                                                                                | Recommended handling                                                                                                                                                                                                                                     |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reclaimStatusChanged` | The reclaim return to the payer on a payment for a merchant under your partner changed. Sent each time the payment's `refundLifecycleStatus` changes, including when the return completes. The payment's `statusCode` does not change. | Record the event and update your reclaim tracking from `data.refundLifecycleStatus` and `data.statusCode`. Do not change the order's fulfillment decision. See [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status.md). |

The same reclaim progress is also delivered as a payment webhook to the terminal webhook URL. Register `reclaimStatusChanged` when you want one partner level feed across all of your merchants.

Design your handler to tolerate additional event types in the future. New event types should not break your webhook consumer.

#### What Bead does not send

* **Rejected applications.** There is no webhook when an application is rejected, and no rejection reason is provided through the API. Check for the `rejected` status with [Get Status](/onboarding/get-status.md).
* **Email or other notifications.** Bead does not email or otherwise notify the signer or the partner when an application moves to `changesRequested`, `rejected`, or `boarded`. Use these webhooks and Get Status to drive any notifications in your own system.

### Configure partner webhooks

To receive partner events, configure a webhook URL for your partner.

You can register, update, or remove a partner webhook configuration.

| Action                                   | Endpoint                                          |
| ---------------------------------------- | ------------------------------------------------- |
| Register webhook configuration           | `POST /merchant-onboarding/{partnerId}/webhook`   |
| Register or update webhook configuration | `PUT /merchant-onboarding/{partnerId}/webhook`    |
| Remove webhook configuration             | `DELETE /merchant-onboarding/{partnerId}/webhook` |

For most integrations, use `PUT` so the same command can create or update the configuration.

### Endpoint

```http
PUT /merchant-onboarding/{partnerId}/webhook
```

### Authentication and headers

These requests use API key authentication.

```http
X-Api-Key: {apiKey}
Content-Type: application/json
Accept: application/json
```

### Path parameter

| Parameter   | Description                                                                                                                                |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `partnerId` | Your partner identifier. The webhook configuration is stored per partner and applies to every application and merchant under that partner. |

### Request body

Use a request body with your webhook endpoint URL and the event types you want to receive.

```json
{
  "endpointUrl": "https://partner.example.com/webhooks/partner",
  "eventTypes": [
    "applicationViewed",
    "applicationSigned",
    "applicationReviewed",
    "applicationChangesRequested",
    "merchantBoarded",
    "settlementHeld",
    "reclaimStatusChanged"
  ]
}
```

### Field details

| Field         | Required | Description                                                                                                    |
| ------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
| `endpointUrl` | Yes      | HTTPS URL of your server that will receive partner webhook callbacks.                                          |
| `eventTypes`  | Yes      | List of event types to deliver to this endpoint. Only the event types you list are sent. See Supported events. |

Your `endpointUrl` should be stable, publicly reachable, HTTPS only, and controlled by your organization.

You can call the `PUT` endpoint more than once to update the same partner's webhook configuration.

### Response

The response confirms the current webhook configuration.

Typical fields include:

* `partnerId`
* `endpointUrl`
* `isEnabled`
* `webhookSecret`
* `eventTypes`

Example response:

```json
{
  "partnerId": "partner_123",
  "endpointUrl": "https://partner.example.com/webhooks/partner",
  "isEnabled": true,
  "webhookSecret": "whsec_example_secret",
  "eventTypes": [
    "applicationViewed",
    "applicationSigned",
    "applicationReviewed",
    "applicationChangesRequested",
    "merchantBoarded",
    "settlementHeld",
    "reclaimStatusChanged"
  ]
}
```

Store the `webhookSecret` securely. Treat it as a secret and do not expose it in client side code.

### Receiving webhook events

After you configure partner webhooks, Bead sends HTTP `POST` requests to your `endpointUrl` when registered events occur.

High level behavior:

* method is `POST`
* content type is JSON
* body contains event metadata plus a summary of the application, settlement, or payment
* your endpoint should return a `2xx` response when processing succeeds

Partner webhook payloads are intentionally lean. They are not full application or merchant records.

Use the webhook payload to:

* identify which application, settlement, or payment changed
* identify what event occurred
* correlate the event to your own merchant or partner records
* trigger follow up actions
* decide whether to fetch the full current state

Use `GET /merchant-onboarding/applications/{applicationId}` when you need the full current state of an application.

### Delivery and retries

These rules apply to every partner webhook event.

If your endpoint returns a `5xx` response, returns `408`, or cannot be reached because of a network error, Bead retries the delivery up to 5 times with exponential backoff, at roughly 2, 4, 8, 16, and 32 seconds. After the fifth retry the delivery is dropped and is not queued again. Responses in the `4xx` range, other than `408`, are not retried.

This is a short retry window. It is different from the payment webhook policy described in [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference.md), which retries for up to about 24 hours. Do not assume a partner event will be redelivered if your endpoint is down for more than a few seconds.

Because a dropped event is not resent, treat webhooks as a notification only. Reconcile your records by calling [Get Status](/onboarding/get-status.md) and reading the application history, by reading your merchants' settlements, or by reading partner payment history, especially after any period when your endpoint was unavailable. Note that the reason in an `applicationChangesRequested` event is not available from Get Status or the application history, so store it when the event arrives.

### Security and verification

Treat partner webhooks as a privileged integration path.

Partner webhooks use the same signature standard as payment webhooks. Every delivery carries an `X-Webhook-Signature` header:

```
X-Webhook-Signature: t=1791236941841,s=Zh5JifnVPvYu4ofG92KN6yR3TxDtI+thgOfyHXMZors=
```

| Component | Meaning                                                                         |
| --------- | ------------------------------------------------------------------------------- |
| `t`       | Unix epoch timestamp in **milliseconds** when Bead generated the event          |
| `s`       | **Base64 encoded** HMAC-SHA256 digest of the signed message `t + "." + rawBody` |

For partner webhooks, the HMAC key is your `webhookSecret` exactly as it was returned when you configured the webhook, taken as UTF-8 bytes. Do not base64 decode it. This differs from payment webhooks, where the terminal `signingSecret` is base64 decoded before use.

Because the signed message includes a timestamp, you can reject stale requests. Confirm that `t` is within 5 minutes of the current time to get built in replay protection.

**Legacy `X-Bead-Signature` header.** Partner webhooks also continue to include the earlier `X-Bead-Signature` header. It is a lowercase hexadecimal HMAC-SHA256 digest of the raw request body alone, computed with your `webhookSecret`, and it has no timestamp. It is unchanged and continues to be sent, so integrations that already verify it keep working without any change. New integrations should verify `X-Webhook-Signature`, and existing integrations should plan to move to it, because the legacy header cannot provide replay protection.

Recommended practices:

* use HTTPS for your webhook endpoint
* accept only `POST` on your webhook route
* preserve the raw request body and request headers
* verify `X-Webhook-Signature` and reject stale or invalid requests
* compare digests using constant time comparison
* validate the request body against expected fields
* optionally restrict source IPs or networks where appropriate for your environment

See [How do I verify that a webhook really came from Bead?](/faqs-and-troubleshooting/webhooks-and-error-codes/how-do-i-verify-that-a-webhook-really-came-from-bead.md) for a full verification walkthrough and code samples for both webhook families.

### Payload format

Application, settlement, and reclaim events use different envelopes. Use `type` to tell them apart.

#### Application events

Application events use a small event envelope with top level metadata and a nested `data` object.

**Top level fields**

| Field           | Type   | Description                                          |
| --------------- | ------ | ---------------------------------------------------- |
| `id`            | string | Unique webhook event identifier.                     |
| `type`          | string | Event type that describes what occurred.             |
| `applicationId` | string | Primary onboarding application identifier.           |
| `createdAt`     | string | Timestamp when the event was created.                |
| `data`          | object | Small application summary associated with the event. |

**`data` object fields**

| Field                    | Type           | Description                                                                          |
| ------------------------ | -------------- | ------------------------------------------------------------------------------------ |
| `data.id`                | string         | Application identifier within the nested payload. This should match `applicationId`. |
| `data.merchantName`      | string \| null | Merchant name associated with the application.                                       |
| `data.partnerName`       | string \| null | Partner name associated with the application.                                        |
| `data.partnerId`         | string         | Partner identifier associated with the application.                                  |
| `data.partnerExternalId` | string \| null | Your external partner reference, when available.                                     |

**Event specific `data` fields**

Some events add fields to `data`.

| Event                         | Field                    | Type           | Description                                                                                                                                                                                            |
| ----------------------------- | ------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `applicationViewed`           | `data.viewedDate`        | string         | When the signer opened the application.                                                                                                                                                                |
| `applicationChangesRequested` | `data.reason`            | string         | Why Bead returned the application for changes. Show it to the merchant or reseller. The text varies, so do not parse it. It is the only place the reason is provided to API integrations, so store it. |
| `applicationChangesRequested` | `data.requestedDate`     | string         | When changes were requested.                                                                                                                                                                           |
| `merchantBoarded`             | `data.merchantId`        | string         | The merchant identifier created at boarding.                                                                                                                                                           |
| `merchantBoarded`             | `data.defaultLocationId` | string \| null | The default location created for the merchant, when one was created.                                                                                                                                   |
| `merchantBoarded`             | `data.defaultTerminalId` | string \| null | The default terminal created for the merchant, when one was created.                                                                                                                                   |
| `merchantBoarded`             | `data.status`            | string         | The application status when the event was sent.                                                                                                                                                        |
| `merchantBoarded`             | `data.boardedDate`       | string         | When boarding completed.                                                                                                                                                                               |

The `data` object can include additional fields, such as status details that depend on the event. Ignore fields you do not recognize.

**Example application event**

Below is an example `applicationSigned` event.

```json
{
  "id": "evt_example_application_signed_001",
  "type": "applicationSigned",
  "applicationId": "app_example_001",
  "createdAt": "2026-03-30T20:46:15.111218Z",
  "data": {
    "id": "app_example_001",
    "merchantName": "Example Merchant",
    "partnerName": "Example Partner",
    "partnerId": "partner_example_001",
    "partnerExternalId": "example-partner-001"
  }
}
```

Below is an example `applicationChangesRequested` event. The `reason` text is illustrative.

```json
{
  "id": "evt_example_application_changes_requested_001",
  "type": "applicationChangesRequested",
  "applicationId": "app_example_001",
  "createdAt": "2026-03-31T15:02:44.512004Z",
  "data": {
    "id": "app_example_001",
    "merchantName": "Example Merchant",
    "partnerName": "Example Partner",
    "partnerId": "partner_example_001",
    "partnerExternalId": "example-partner-001",
    "reason": "A required business detail is missing or invalid.",
    "requestedDate": "2026-03-31T15:02:44.511873Z"
  }
}
```

#### Settlement events

Settlement events carry the settlement itself in `data`.

| Field          | Type   | Description                                                                                                                                                                                                      |
| -------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`           | string | Unique webhook event identifier. A new value is generated for every send.                                                                                                                                        |
| `type`         | string | `settlementHeld`.                                                                                                                                                                                                |
| `settlementId` | string | Identifier of the settlement that was held.                                                                                                                                                                      |
| `merchantId`   | string | Identifier of the merchant the settlement belongs to.                                                                                                                                                            |
| `createdAt`    | string | Timestamp when the event was created.                                                                                                                                                                            |
| `data`         | object | The settlement record, with the same fields returned by the [Settlements](/settlement/settlements.md) endpoints. `data.status` shows the hold, and `data.holdReason` carries the reason the settlement was held. |

Settlement events do not include `applicationId`. Correlate them using `settlementId` and `merchantId`.

#### Reclaim events

Reclaim events carry the payment in `data`, using the same payload as a payment webhook.

| Field        | Type   | Description                                                                                                                                                                                                    |
| ------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`         | string | Unique webhook event identifier. A new value is generated for every send.                                                                                                                                      |
| `type`       | string | `reclaimStatusChanged`.                                                                                                                                                                                        |
| `paymentId`  | string | Identifier of the payment whose reclaim changed.                                                                                                                                                               |
| `merchantId` | string | Identifier of the merchant the payment belongs to.                                                                                                                                                             |
| `createdAt`  | string | Timestamp when the event was created.                                                                                                                                                                          |
| `data`       | object | The payment, with the same fields as a [payment webhook](/payments/payment-webhooks.md), including `statusCode`, `refundLifecycleStatus`, `refundInitiatedAt`, `refundCompletedAt`, and `refundFailureReason`. |

**Example reclaim event**

Below is an example `reclaimStatusChanged` event for an overpaid payment whose excess was returned to the payer. `statusCode` stays `overpaid`. The `data` object is abbreviated.

```json
{
  "id": "evt_5b1f0c2a9e7d4a6c8f3b2d1e0a9c8b7d",
  "type": "reclaimStatusChanged",
  "paymentId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
  "merchantId": "664c5e3b0517b0a8a6321c9a",
  "createdAt": "2026-10-06T14:20:42.118Z",
  "data": {
    "paymentId": "c10b29e3c8104e0f8dc139c20d9eeb6c",
    "paymentCode": "PMT-10452",
    "statusCode": "overpaid",
    "terminalId": "65f0b274f9a4c9a3f8d4b2a1",
    "merchantId": "664c5e3b0517b0a8a6321c9a",
    "refundLifecycleStatus": "completed",
    "refundInitiatedAt": "2026-10-06T13:57:20.207Z",
    "refundCompletedAt": "2026-10-06T14:20:41.000Z",
    "refundFailureReason": null
  }
}
```

Reclaim events do not include `applicationId`. Correlate them using `paymentId` and `merchantId`.

### Field guidance

#### `id`

This is the webhook event identifier. Use it as the primary event level idempotency key for application events.

#### `type`

This tells you what happened.

Supported event types include:

* `applicationViewed`
* `applicationSigned`
* `applicationReviewed` (not currently sent)
* `applicationChangesRequested`
* `merchantBoarded`
* `settlementHeld`
* `reclaimStatusChanged`

Use `type` as the primary event classifier, but do not treat it as the complete current state. Call Get Status when your system needs to know the current application status.

#### `applicationId`

This is the primary application correlation key for application events.

Use it to:

* link the webhook to your internal onboarding record
* call Get Status for full application detail
* reconcile lifecycle changes over time
* support internal dashboards and support workflows

#### `createdAt`

This is the event timestamp. It is useful for event ordering, auditing, and deduplication.

Do not rely on events arriving in strict chronological order. Use `createdAt` for audit context and read the current state to confirm it.

#### `data`

For application events, this object contains a lightweight application summary.

It is useful for:

* displaying a readable merchant or partner name in logs and dashboards
* routing to the correct partner context
* correlating internal references without requiring an immediate follow up API call

### Event handling expectations

At minimum, your webhook handler should store:

* `id`
* `type`
* `createdAt`
* `applicationId` for application events, `settlementId` and `merchantId` for settlement events, or `paymentId` and `merchantId` for reclaim events

In most integrations, you should also store:

* `data.partnerId`
* `data.partnerExternalId`
* `data.merchantName`
* `data.reason` for `applicationChangesRequested` events
* the raw request body
* the request headers
* your processing result

Treat the payload as an event trigger, not a source of full truth.

### Recommended processing model

A recommended webhook processing model is:

1. Receive the request.
2. Preserve the raw request body and headers.
3. Verify authenticity using `X-Webhook-Signature` and your `webhookSecret`.
4. Parse the JSON payload.
5. Use `id` as the event identifier, and `applicationId`, `settlementId`, or `paymentId` as the correlation key.
6. Apply idempotent processing so duplicate deliveries do not create duplicate actions.
7. Read the current state when you need it.
8. Update your internal systems.
9. Return a `2xx` response quickly.

### Idempotency and duplicate handling

Treat webhook delivery as at least once.

A good idempotent processing strategy for application events is:

* primary event key: `id`
* fallback key: `applicationId` + `type` + `createdAt`

For settlement events, the `id` is generated fresh on every send, so a resent event carries a different `id`. Use `settlementId` + `type` as your key instead.

For reclaim events, the `id` is also generated fresh on every send. Use `paymentId` + `data.refundLifecycleStatus` as your key.

Your system should be able to safely ignore or reprocess duplicate webhook deliveries without creating duplicate tasks, records, or notifications.

### Using webhooks with Get Status

Webhooks and [Get Status](/onboarding/get-status.md) work best together.

Use the webhook to:

* detect that something changed
* know which application changed
* route the event internally
* trigger follow up workflows

Use Get Status to:

* retrieve the current full application state
* confirm the latest status before irreversible actions
* check for `onboardedMerchantId` after `merchantBoarded`
* surface complete details in your portal or internal tools

This is especially important because application events are intentionally smaller than the full application record.

### Mapping events to application status

Webhook event types and application statuses are related, but they are not the same thing.

| Webhook event                 | Related status context                                                                                 | Recommended action                                                                                          |
| ----------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| `applicationViewed`           | Associated with the `viewed` status.                                                                   | Record the event. Call Get Status if you need the current status.                                           |
| `applicationSigned`           | Often associated with signer completion and statuses such as `signed`, `reviewing`, or later states.   | Call Get Status and update your internal record using the returned status.                                  |
| `applicationReviewed`         | Not currently sent. Review progress shows in statuses such as `reviewing`, `reviewed`, and `boarding`. | Use Get Status to follow review progress.                                                                   |
| `applicationChangesRequested` | Associated with the `changesRequested` status.                                                         | Store `data.reason`, show it to the merchant or reseller, then resend the application once the fix is made. |
| `merchantBoarded`             | Usually associated with the `boarded` status and presence of `onboardedMerchantId`.                    | Call Get Status, store `onboardedMerchantId`, and proceed with downstream merchant workflows.               |
| `settlementHeld`              | Not tied to an application status. It relates to a merchant's settlement.                              | Record the event, read `data.holdReason`, and review the merchant's settlements.                            |
| `reclaimStatusChanged`        | Not tied to an application status. It relates to a payment's reclaim return.                           | Record the event and update your reclaim tracking from `data.refundLifecycleStatus`.                        |

Do not assume the webhook event alone contains the final current status. Always call Get Status when the next action depends on the exact current application status.

### Using webhooks with manual risk and review workflows

Webhooks also complement manual risk, review, and operations workflows.

A common pattern is:

1. receive a partner webhook event
2. read the latest state
3. surface the current state to your operations team or merchant facing portal
4. take the next internal action based on the returned status and, for `applicationChangesRequested`, the reason in the event

This is especially useful when an application needs review, follow up, or operational investigation.

### Testing your partner webhooks

Before going live:

1. Implement a simple listener endpoint that logs incoming requests and returns `200 OK`.
2. Configure your Sandbox partner to use that endpoint.
3. Include the event types you want to test.
4. Submit test onboarding applications.
5. Capture real webhook events.
6. Confirm your signature verification passes, and that a tampered request is rejected.
7. Confirm your processing is idempotent.
8. Confirm your system can use `applicationId` to fetch full application state.

Application events fire as test applications move through viewing, signing, and boarding. A `settlementHeld` event is sent only when a settlement actually enters hold, so it cannot be triggered on demand. A `reclaimStatusChanged` event is sent when a payer claims and receives a reclaim, for example after a test payment ends as `overpaid` or `underpaid`. Contact your Bead team if you want to coordinate a settlement or reclaim event test.

Recommended application events to test over time:

* `applicationViewed`
* `applicationSigned`
* `applicationChangesRequested`
* `merchantBoarded`

For a complete Sandbox workflow test, use [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox.md) and confirm your webhook listener receives the expected lifecycle events as the application moves through signing and boarding.

### Troubleshooting

#### I am not receiving partner webhook events

Check:

* your partner has an enabled webhook registration
* the webhook is configured on the expected `partnerId`
* the applications or merchants belong to that partner
* the configured `eventTypes` include the event you expect to receive
* your endpoint is publicly reachable
* your endpoint accepts `POST`
* your endpoint returns a `2xx` response
* your endpoint does not time out

If your endpoint was unavailable, remember that a failed delivery is retried only 5 times over about a minute and is then dropped. Call [Get Status](/onboarding/get-status.md) to catch up on applications, read your merchants' settlements to catch up on holds, and read partner payment history to catch up on reclaims.

#### I am not receiving applicationReviewed events

`applicationReviewed` is a valid event type, but Bead does not currently send it. Use Get Status to follow review progress.

#### I received a webhook but need more application detail

Use `applicationId` and call:

```http
GET /merchant-onboarding/applications/{applicationId}
```

#### I am unsure how to verify authenticity

Use the `webhookSecret` from your webhook configuration as UTF-8 bytes, and verify the `X-Webhook-Signature` header as described in Security and verification. See [How do I verify that a webhook really came from Bead?](/faqs-and-troubleshooting/webhooks-and-error-codes/how-do-i-verify-that-a-webhook-really-came-from-bead.md) for the full steps and a code sample.

#### My signature check fails

The most common causes are base64 decoding the `webhookSecret` (partner secrets are used as issued, not decoded), signing the body alone instead of `t + "." + rawBody`, and verifying a parsed and re serialized body instead of the raw bytes.

#### The webhook payload does not include everything I need

This is expected. Partner webhook payloads are designed as lightweight event notifications.

Use them to trigger follow up logic, then read the current state when you need the full record.

#### I received the same webhook more than once

This can happen with at least once webhook delivery.

Use the idempotency keys described above. Your system should safely ignore duplicates or reprocess them without creating duplicate tasks, records, emails, or status changes.

#### I received an event type I do not recognize

Store the event, avoid failing your handler, and return `2xx` if the request is otherwise valid.

Then read the current state using `applicationId`, `settlementId`, or `paymentId` to determine what changed.

### Best practices

* Treat partner webhook events as event notifications, not full records.
* Register the event types your system is prepared to process, and remember that only registered event types are delivered.
* Use `id` as the primary event identifier for application events, `settlementId` for settlement events, and `paymentId` with `data.refundLifecycleStatus` for reclaim events.
* Verify `X-Webhook-Signature` before processing any payload, and reject stale timestamps.
* Store `data.reason` from every `applicationChangesRequested` event, because it is not available from Get Status or the application history.
* Return a `2xx` response quickly.
* Make your consumer idempotent.
* Keep your webhook endpoint HTTPS only.
* Store `webhookSecret` securely.
* Reconcile with Get Status, the application history, merchant settlements, and partner payment history, because a dropped delivery is not resent.
* Capture and test real Sandbox events before Production.
* Call Get Status before taking irreversible or operationally sensitive actions.
* Design your handler to tolerate new event types or additional fields in the future.

### Related pages

* [Submit Application](/onboarding/submit-application.md)
* [Fee Configuration for Onboarding Applications](/onboarding/fee-configuration-for-onboarding-applications.md)
* [Application Attachments](/onboarding/application-attachments.md)
* [Test the Full Onboarding Workflow in Sandbox](/onboarding/test-the-full-onboarding-workflow-in-sandbox.md)
* [Get Status](/onboarding/get-status.md)
* [Resend Application](/onboarding/resend-application.md)
* [Sample Payload](/onboarding/sample-payload.md)
* [Settlements](/settlement/settlements.md)
* [Payment Webhooks](/payments/payment-webhooks.md)
* [Tracking Reclaim Status](/reference-guide/payment-scenarios/tracking-reclaim-status.md)
* [Webhook Event Reference](/reference-guide/operational-guides/webhook-event-reference.md)
* [How do I verify that a webhook really came from Bead?](/faqs-and-troubleshooting/webhooks-and-error-codes/how-do-i-verify-that-a-webhook-really-came-from-bead.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developers.bead.xyz/onboarding/partner-webhooks.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
