> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cdp.coinbase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Customers Webhooks

> Subscribe to customer capability and deletion webhooks for real-time KYC lifecycle updates.

Customer webhooks provide your app with real-time updates as Coinbase processes a customer's KYC and compliance lifecycle. Subscribe your endpoint to react to capability changes and customer deletions without polling.

Because verification and compliance reviews are asynchronous, webhooks are the recommended production pattern: a capability can stay `pending` briefly after all requirements are submitted, and ongoing monitoring can change a customer's state at any time. See [Capabilities](/customers-kyc/capabilities) and [Requirements](/customers-kyc/requirements) for the underlying lifecycle.

## Setup

### Prerequisites

<AccordionGroup>
  <Accordion title="A Secret API Key">
    Sign up at [portal.cdp.coinbase.com](https://portal.cdp.coinbase.com), then navigate to [API Keys](https://portal.cdp.coinbase.com/projects/api-keys) and select **Create API key** under the **Secret API Keys** tab.

    1. Enter an API key nickname (restrictions are optional)
    2. Click **Create**
    3. Secure your API Key ID and Secret in a safe location
  </Accordion>

  <Accordion title="A webhook URL">
    You'll need an HTTPS URL to receive webhook events. For quick testing, [webhook.site](https://webhook.site) gives free temporary URLs instantly.

    For production, use your own HTTPS endpoint.
  </Accordion>

  <Accordion title="CDP CLI">
    The [CDP CLI](/get-started/build-with-ai/cdp-for-agents) handles authentication and exposes webhook subscription endpoints as typed commands (`cdp data webhooks subscriptions create`, `list`, `update`, `delete`). For raw HTTP, use `cdp api`.

    ```bash theme={null}
    npm install -g @coinbase/cdp-cli
    cdp env live --key-file ./cdp_api_key.json
    cdp data webhooks subscriptions list
    ```
  </Accordion>
</AccordionGroup>

<Tip>
  **Testing in Sandbox:** Webhook subscriptions behave the same in Sandbox, so you can exercise the full flow without real customer data. Two adjustments to the prerequisites above:

  * **A Secret API Key:** create it in the **Sandbox** environment of the [Portal](https://portal.cdp.coinbase.com) rather than **Live**.
  * **CDP CLI:** configure a Sandbox environment pointed at the Sandbox platform base (`/platform/v2`) once, then run subscription commands against that environment.

  ```bash theme={null}
  # One-time: create a CLI environment named "sandbox" pointed at the Sandbox host
  cdp env sandbox --key-file ./cdp-api-key.json --url https://sandbox.cdp.coinbase.com/platform/v2

  cdp data webhooks subscriptions list
  ```

  To generate test deliveries, change a customer's capabilities with the sandbox [magic SSN values](/customers-kyc/requirements#testing-in-sandbox) — for example, the approve value activates the requested capabilities, and every capability status change emits `customers.capability.changed`. The [Customers Quickstart](/customers-kyc/quickstart) walks a full sandbox flow that exercises these transitions.
</Tip>

### Event types

Subscribe to the customer event types relevant to your integration:

| Event type                     | Description                                                                                                     |
| :----------------------------- | :-------------------------------------------------------------------------------------------------------------- |
| `customers.capability.changed` | One or more capability statuses changed for a customer. A single event can report several capabilities at once. |
| `customers.customer.deleted`   | The customer record was deleted. This transition is terminal.                                                   |

### Create a webhook subscription

Configuration notes:

* `target.url` should be your HTTPS webhook endpoint that will receive the events.
* You can set a `headers` object in `target` if your URL requires specific headers. Pass it inline with `'target.headers:={"custom-header":"value"}'`.
* Include every customer event type you want to receive so you do not miss a state change.

Create the webhook subscription with the CDP CLI:

```bash lines theme={null}
cdp data webhooks subscriptions create \
  'eventTypes:=["customers.capability.changed","customers.customer.deleted"]' \
  isEnabled:=true \
  target.url=https://your-webhook-url.com \
  target.method=POST
```

Sample webhook subscription response:

```bash lines wrap theme={null}
201 Created
{
  "createdAt": "2026-06-23T22:31:26.157325Z",
  "eventTypes": [
    "customers.capability.changed",
    "customers.customer.deleted"
  ],
  "isEnabled": true,
  "labels": {
    "entity": "<YOUR_ENTITY_ID>"
  },
  "metadata": {
    "secret": "<SECRET_FOR_WEBHOOK_VERIFICATION>"
  },
  "secret": "<SECRET_FOR_WEBHOOK_VERIFICATION>",
  "subscriptionId": "<YOUR_SUBSCRIPTION_ID>",
  "target": {
    "url": "https://your-webhook-url.com"
  }
}
```

## Manage subscriptions

Use the `subscriptionId` from the response to view, update, or delete the subscription.

<b>List all subscriptions</b>

```bash theme={null}
cdp data webhooks subscriptions list
```

<b>View specified subscription details by subscription ID</b>

```bash theme={null}
cdp data webhooks subscriptions get <SUBSCRIPTION_ID>
```

<b>Update subscription</b>

`update` is a full replace. Pass every field, including ones you aren't changing:

```bash lines theme={null}
cdp data webhooks subscriptions update <SUBSCRIPTION_ID> \
  'eventTypes:=["customers.capability.changed","customers.customer.deleted"]' \
  isEnabled:=true \
  target.url=https://your-new-webhook-url.com \
  target.method=POST
```

<b>Delete subscription</b>

```bash theme={null}
cdp data webhooks subscriptions delete <SUBSCRIPTION_ID>
```

### Webhook signature verification

Use the standard CDP webhook verification flow. The same code works for all webhook event domains. See [Verify signatures](/webhooks/verify-signatures) for the full step-by-step implementation.

## Event payloads

Coinbase POSTs a JSON body containing **only the event-specific fields**. There is no outer envelope (`eventId`, `eventType`, `timestamp`, or `data` wrapper) in the request body.

Routing and idempotency metadata arrive in HTTP headers instead:

| Header              | Description                                                                                                  |
| :------------------ | :----------------------------------------------------------------------------------------------------------- |
| `X-Event-Id`        | Unique identifier for this delivery. Use it for idempotency. The same event may be delivered more than once. |
| `X-Event-Type`      | The event type string (for example, `customers.capability.changed`).                                         |
| `X-Hook0-Signature` | Signature for verifying the request. See [Verify signatures](/webhooks/verify-signatures).                   |

Verify signatures against the **raw request body bytes** exactly as received. Parsing JSON before verification will break signature validation.

```text title="Example request" theme={null}
POST /webhook HTTP/1.1
Content-Type: application/json
X-Event-Id: 3a7f8b2e-1c4d-4e9a-b5f6-d8a2c0e7f194
X-Event-Type: customers.capability.changed
X-Hook0-Signature: t=1728394718,v0=9f8e7d6c5b4a...,h=content-type x-event-id x-event-type,v1=a1b2c3d4e5f6...

{ ... event payload JSON ... }
```

#### `customers.capability.changed`

Emitted when the status of one or more capabilities changes. Each change is a distinct entry in the `capabilities` array.

```json lines wrap theme={null}
{
  "customerId": "customer_38d54acb-0868-431c-a88e-1ffcb62655eb",
  "capabilities": [
    { "code": "custodyFiat", "status": "active" },
    { "code": "transferFiat", "status": "active" }
  ]
}
```

#### `customers.customer.deleted`

Emitted when the customer record is deleted. This transition is terminal.

```json lines wrap theme={null}
{
  "customerId": "customer_38d54acb-0868-431c-a88e-1ffcb62655eb"
}
```

## Best practices

To ensure reliable webhook delivery:

* **Test endpoints locally** before enabling subscriptions in production.
* **Handle concurrent requests.** Ensure your target URL can process multiple events simultaneously.
* **Process events asynchronously.** Return a `200` response quickly and process the event in the background.
* **Use `X-Event-Id` for idempotency.** The same event may be delivered more than once.
* **Monitor webhook receiver health.** Track delivery success rates to your target URL.
* **Set up subscription monitoring.** Use a scheduled job (for example, cron or a systemd timer) to periodically call the List Subscriptions API and verify critical subscriptions have `isEnabled: true`.

<Note>
  Subscriptions may be automatically disabled if your endpoint experiences sustained delivery failures (e.g., high failure rates, endpoint unavailability, or throughput issues). If this happens, fix the underlying endpoint issue and use the [Update Subscription API](/api-reference/v2/rest-api/webhooks/update-webhook-subscription) to re-enable.
</Note>
