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

# Create Webhook Endpoint

> Create a new webhook configuration for receiving event notifications

# Create Webhook Endpoint

POST [https://api.baanx.com/v1/webhooks](https://api.baanx.com/v1/webhooks)
Creates a new webhook configuration for receiving event notifications.

## Overview

Registers a new HTTPS endpoint to receive webhook deliveries. Each webhook can subscribe to one or more event types and is assigned a unique signing key for signature verification.

<Warning>
  **Save the API key immediately.** The full API key is returned only at creation time and cannot be retrieved again. If lost, use the [Rotate Key](/api-reference/webhooks/rotate-key) endpoint to generate a new one.
</Warning>

<Note>
  **Limits and defaults:**

  * Maximum of **5 webhook endpoints** per partner
  * New webhooks are created with `is_active: false` by default unless explicitly set
  * Only **HTTPS** URLs are accepted
</Note>

## Signature Verification

All webhook deliveries include HMAC-SHA256 signature headers:

| Header        | Description                                   |
| ------------- | --------------------------------------------- |
| `X-Timestamp` | Unix timestamp of the request                 |
| `X-Signature` | HMAC-SHA256 signature of `{timestamp}.{body}` |

Verify webhooks by computing:

```
HMAC-SHA256(apiKey, "{timestamp}.{body}")
```

## Authentication

This endpoint requires authentication via Bearer token:

```bash theme={null} theme={null}
Authorization: Bearer YOUR_ACCESS_TOKEN
```

## Request

### Headers

<ParamField header="Authorization" type="string" required>
  Bearer token for authentication
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`
</ParamField>

### Body

<ParamField body="name" type="string" required>
  Human-readable name for the webhook (max 255 characters)
</ParamField>

<ParamField body="url" type="string" required>
  HTTPS endpoint URL for webhook delivery. Must use HTTPS.
</ParamField>

<ParamField body="event_types" type="array" required>
  Array of event type strings to subscribe to. Must contain at least one item.

  **Available event types:**

  * `kyc.status.changed` - User KYC verification status changed
  * `card.activated` - Card has been activated
  * `transaction.cleared` - Transaction has been cleared
</ParamField>

<ParamField body="is_active" type="boolean" default={false}>
  Whether to activate the webhook immediately. Defaults to `false`.
</ParamField>

<ParamField body="metadata" type="object">
  Optional custom metadata to attach to the webhook (e.g., environment tags)
</ParamField>

### Request Examples

<CodeGroup>
  ```bash cURL theme={null} theme={null}
  curl -X POST https://api.baanx.com/v1/webhooks \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "KYC Status Updates",
      "url": "https://api.partner.com/webhooks/kyc",
      "event_types": ["kyc.status.changed"],
      "is_active": true
    }'
  ```

  ```javascript JavaScript theme={null} theme={null}
  const response = await fetch('https://api.baanx.com/v1/webhooks', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'KYC Status Updates',
      url: 'https://api.partner.com/webhooks/kyc',
      event_types: ['kyc.status.changed'],
      is_active: true
    })
  });

  const data = await response.json();
  // ⚠️ Store data.data.apiKey securely - it won't be shown again
  console.log(data);
  ```

  ```python Python theme={null} theme={null}
  import requests

  url = "https://api.baanx.com/v1/webhooks"
  headers = {
      "Authorization": "Bearer YOUR_ACCESS_TOKEN",
      "Content-Type": "application/json"
  }
  payload = {
      "name": "KYC Status Updates",
      "url": "https://api.partner.com/webhooks/kyc",
      "event_types": ["kyc.status.changed"],
      "is_active": True
  }

  response = requests.post(url, headers=headers, json=payload)
  data = response.json()
  # ⚠️ Store data["data"]["apiKey"] securely - it won't be shown again
  print(data)
  ```

  ```typescript TypeScript theme={null} theme={null}
  interface CreateWebhookRequest {
    name: string;
    url: string;
    event_types: string[];
    is_active?: boolean;
    metadata?: Record<string, unknown>;
  }

  const createWebhook = async (payload: CreateWebhookRequest) => {
    const response = await fetch('https://api.baanx.com/v1/webhooks', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(payload)
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const result = await response.json();
    // ⚠️ Store result.data.apiKey securely - it won't be shown again
    return result;
  };
  ```
</CodeGroup>

## Response

### 201 Created

<Warning>
  Store the `apiKey` from the response immediately and securely. It will **not** be shown again.
</Warning>

<ResponseField name="success" type="boolean">
  Indicates the webhook was created successfully
</ResponseField>

<ResponseField name="data.id" type="string (UUID)">
  Unique identifier for the new webhook
</ResponseField>

<ResponseField name="data.apiKey" type="string">
  **Full API key** — store securely. Used to verify webhook signatures. Not retrievable after this response.
</ResponseField>

<ResponseField name="data.eventTypes" type="array">
  Event types the webhook is subscribed to
</ResponseField>

<ResponseField name="data.isActive" type="boolean">
  Whether the webhook is active
</ResponseField>

<ResponseExample>
  ```json 201 - Created theme={null} theme={null}
  {
    "success": true,
    "data": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "tenant": "partner-name",
      "name": "KYC Status Updates",
      "url": "https://api.partner.com/webhooks/kyc",
      "apiKey": "whk_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2",
      "eventTypes": ["kyc.status.changed"],
      "isActive": true,
      "metadata": {},
      "createdAt": "2025-12-29T10:00:00.000Z",
      "updatedAt": "2025-12-29T10:00:00.000Z"
    }
  }
  ```
</ResponseExample>

## Error Responses

<ResponseExample>
  ```json 400 - Webhook Limit Reached theme={null} theme={null}
  {
    "message": "Maximum webhook limit (5) reached for this tenant"
  }
  ```

  ```json 400 - Validation Error theme={null} theme={null}
  {
    "message": "url must be an HTTPS URL"
  }
  ```

  ```json 401 - Unauthorized theme={null} theme={null}
  {
    "message": "Not authenticated"
  }
  ```

  ```json 403 - Forbidden theme={null} theme={null}
  {
    "message": "Not authorized"
  }
  ```

  ```json 503 - Service Unavailable theme={null} theme={null}
  {
    "message": "Notification service is not configured for this environment"
  }
  ```
</ResponseExample>

## Common Error Scenarios

<AccordionGroup>
  <Accordion title="Webhook Limit Reached">
    **Error:** `400 Bad Request`

    **Message:** `"Maximum webhook limit (5) reached for this tenant"`

    **Cause:** You already have 5 webhook endpoints configured.

    **Solution:** Delete an existing webhook with `DELETE /v1/webhooks/{id}` before creating a new one.
  </Accordion>

  <Accordion title="Non-HTTPS URL">
    **Error:** `400 Bad Request`

    **Cause:** The `url` field does not use the HTTPS scheme.

    **Solution:** Ensure your endpoint URL begins with `https://`.
  </Accordion>
</AccordionGroup>

## Related Endpoints

* `GET /v1/webhooks` - List all webhook endpoints
* `PUT /v1/webhooks/{id}` - Update a webhook configuration
* `POST /v1/webhooks/{id}/rotate-key` - Rotate the signing key
* `DELETE /v1/webhooks/{id}` - Delete a webhook endpoint
