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

# Webhook Setup

> Configure webhook endpoints to receive asynchronous payment notifications

## Overview

Webhooks provide an asynchronous alternative to the 90-second long-polling model used by the standard payment endpoint. Instead of waiting for a terminal response, you can receive payment results via HTTP callbacks to your server.

**When to use webhooks:**

* Your integration cannot handle 90-second HTTP timeouts
* You prefer event-driven architecture
* You need to process payments asynchronously

<Warning>
  Endpoint URLs must be publicly reachable. When you create an endpoint, the system sends a validation ping to verify connectivity. Endpoints that fail this ping test cannot be created.
</Warning>

***

## Endpoint Limits

Each group can configure up to **16 webhook endpoints**. This limit applies across all event type subscriptions.

***

## Managing Webhook Endpoints

### Create Endpoint

Register a new webhook endpoint to receive payment notifications.

```
POST /v1/webhooks/endpoints
```

#### Request Body

<ParamField body="url" type="string" required>
  The HTTPS URL where webhook payloads will be delivered. Must be publicly accessible.

  **Example:** `"https://api.yourcompany.com/webhooks/payments"`
</ParamField>

<ParamField body="events" type="string[]" required>
  Array of event types to subscribe to.

  **Values:** `payment.completed`, `payment.failed`, `payment.cancelled`, `payment.timeout`
</ParamField>

<ParamField body="description" type="string">
  Human-readable description for this endpoint.

  **Example:** `"Production payment notifications"`
</ParamField>

<ParamField body="metadata" type="object">
  Custom key-value pairs to associate with this endpoint.
</ParamField>

#### Request Example

```json theme={null}
{
  "url": "https://api.yourcompany.com/webhooks/payments",
  "events": ["payment.completed", "payment.failed", "payment.cancelled", "payment.timeout"],
  "description": "Production payment notifications",
  "metadata": {
    "environment": "production"
  }
}
```

#### Response (201 Created)

```json theme={null}
{
  "endpointId": "wh_01HQ3K4M5N6P7R8S9T0UVWXYZ",
  "url": "https://api.yourcompany.com/webhooks/payments",
  "events": ["payment.completed", "payment.failed", "payment.cancelled", "payment.timeout"],
  "description": "Production payment notifications",
  "secret": "whsec_abc123xyz789...",
  "status": "active",
  "metadata": {
    "environment": "production"
  },
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}
```

<Note>
  The `secret` is only returned when creating the endpoint. Store it securely for signature verification. You cannot retrieve it later.
</Note>

#### Code Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://{your-api-endpoint}/v1/webhooks/endpoints" \
    -H "Content-Type: application/json" \
    -H "x-api-key: your-api-key" \
    -H "x-timestamp: 2024-01-15T10:30:00.000Z" \
    -H "x-signature: <computed-signature>" \
    -d '{
      "url": "https://api.yourcompany.com/webhooks/payments",
      "events": ["payment.completed", "payment.failed"],
      "description": "Production notifications"
    }'
  ```

  ```javascript Node.js theme={null}
  async function createWebhookEndpoint() {
    const path = '/v1/webhooks/endpoints';
    const body = {
      url: 'https://api.yourcompany.com/webhooks/payments',
      events: ['payment.completed', 'payment.failed'],
      description: 'Production notifications'
    };
    const headers = generateAuthHeaders('POST', path, body);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'POST',
      headers,
      body: JSON.stringify(body)
    });

    const data = await response.json();
    // Store data.secret securely for signature verification
    return data;
  }
  ```

  ```python Python theme={null}
  def create_webhook_endpoint():
      path = '/v1/webhooks/endpoints'
      body = {
          'url': 'https://api.yourcompany.com/webhooks/payments',
          'events': ['payment.completed', 'payment.failed'],
          'description': 'Production notifications'
      }
      headers = generate_auth_headers('POST', path, body)

      response = requests.post(f'{BASE_URL}{path}', headers=headers, json=body)
      data = response.json()
      # Store data['secret'] securely for signature verification
      return data
  ```
</CodeGroup>

***

### List Endpoints

Retrieve all webhook endpoints for your group.

```
GET /v1/webhooks/endpoints
```

#### Response (200 OK)

```json theme={null}
{
  "endpoints": [
    {
      "endpointId": "wh_01HQ3K4M5N6P7R8S9T0UVWXYZ",
      "url": "https://api.yourcompany.com/webhooks/payments",
      "events": ["payment.completed", "payment.failed", "payment.cancelled", "payment.timeout"],
      "description": "Production payment notifications",
      "status": "active",
      "metadata": {
        "environment": "production"
      },
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T10:30:00.000Z"
    }
  ],
  "count": 1,
  "timestamp": "2024-01-15T10:35:00.000Z"
}
```

#### Code Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://{your-api-endpoint}/v1/webhooks/endpoints" \
    -H "x-api-key: your-api-key" \
    -H "x-timestamp: 2024-01-15T10:35:00.000Z" \
    -H "x-signature: <computed-signature>"
  ```

  ```javascript Node.js theme={null}
  async function listWebhookEndpoints() {
    const path = '/v1/webhooks/endpoints';
    const headers = generateAuthHeaders('GET', path);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'GET',
      headers
    });

    return response.json();
  }
  ```

  ```python Python theme={null}
  def list_webhook_endpoints():
      path = '/v1/webhooks/endpoints'
      headers = generate_auth_headers('GET', path)

      response = requests.get(f'{BASE_URL}{path}', headers=headers)
      return response.json()
  ```
</CodeGroup>

***

### Get Endpoint

Retrieve a specific webhook endpoint by ID.

```
GET /v1/webhooks/endpoints/{endpointId}
```

#### Path Parameters

<ParamField path="endpointId" type="string" required>
  The unique identifier of the webhook endpoint.
</ParamField>

#### Response (200 OK)

```json theme={null}
{
  "endpointId": "wh_01HQ3K4M5N6P7R8S9T0UVWXYZ",
  "url": "https://api.yourcompany.com/webhooks/payments",
  "events": ["payment.completed", "payment.failed", "payment.cancelled", "payment.timeout"],
  "description": "Production payment notifications",
  "status": "active",
  "metadata": {
    "environment": "production"
  },
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z"
}
```

***

### Update Endpoint

Update an existing webhook endpoint's configuration.

```
PUT /v1/webhooks/endpoints/{endpointId}
```

#### Path Parameters

<ParamField path="endpointId" type="string" required>
  The unique identifier of the webhook endpoint.
</ParamField>

#### Request Body

<ParamField body="url" type="string">
  Updated HTTPS URL for webhook delivery. A validation ping is sent to verify the new URL.
</ParamField>

<ParamField body="events" type="string[]">
  Updated array of event types to subscribe to.
</ParamField>

<ParamField body="description" type="string">
  Updated description for this endpoint.
</ParamField>

<ParamField body="status" type="string">
  Endpoint status. Set to `disabled` to pause webhook delivery without deleting the endpoint.

  **Values:** `active`, `disabled`
</ParamField>

<ParamField body="metadata" type="object">
  Updated custom metadata.
</ParamField>

#### Request Example

```json theme={null}
{
  "events": ["payment.completed", "payment.failed"],
  "status": "active"
}
```

#### Response (200 OK)

```json theme={null}
{
  "endpointId": "wh_01HQ3K4M5N6P7R8S9T0UVWXYZ",
  "url": "https://api.yourcompany.com/webhooks/payments",
  "events": ["payment.completed", "payment.failed"],
  "description": "Production payment notifications",
  "status": "active",
  "metadata": {
    "environment": "production"
  },
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T11:00:00.000Z"
}
```

#### Code Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT "https://{your-api-endpoint}/v1/webhooks/endpoints/wh_01HQ3K4M5N6P7R8S9T0UVWXYZ" \
    -H "Content-Type: application/json" \
    -H "x-api-key: your-api-key" \
    -H "x-timestamp: 2024-01-15T11:00:00.000Z" \
    -H "x-signature: <computed-signature>" \
    -d '{
      "events": ["payment.completed", "payment.failed"],
      "status": "active"
    }'
  ```

  ```javascript Node.js theme={null}
  async function updateWebhookEndpoint(endpointId, updates) {
    const path = `/v1/webhooks/endpoints/${endpointId}`;
    const headers = generateAuthHeaders('PUT', path, updates);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'PUT',
      headers,
      body: JSON.stringify(updates)
    });

    return response.json();
  }

  // Usage
  await updateWebhookEndpoint('wh_01HQ3K4M5N6P7R8S9T0UVWXYZ', {
    events: ['payment.completed', 'payment.failed'],
    status: 'active'
  });
  ```

  ```python Python theme={null}
  def update_webhook_endpoint(endpoint_id: str, updates: dict):
      path = f'/v1/webhooks/endpoints/{endpoint_id}'
      headers = generate_auth_headers('PUT', path, updates)

      response = requests.put(f'{BASE_URL}{path}', headers=headers, json=updates)
      return response.json()

  # Usage
  update_webhook_endpoint('wh_01HQ3K4M5N6P7R8S9T0UVWXYZ', {
      'events': ['payment.completed', 'payment.failed'],
      'status': 'active'
  })
  ```
</CodeGroup>

***

### Delete Endpoint

Remove a webhook endpoint. This action is permanent.

```
DELETE /v1/webhooks/endpoints/{endpointId}
```

#### Path Parameters

<ParamField path="endpointId" type="string" required>
  The unique identifier of the webhook endpoint.
</ParamField>

#### Response (204 No Content)

No response body is returned on successful deletion.

#### Code Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE "https://{your-api-endpoint}/v1/webhooks/endpoints/wh_01HQ3K4M5N6P7R8S9T0UVWXYZ" \
    -H "x-api-key: your-api-key" \
    -H "x-timestamp: 2024-01-15T12:00:00.000Z" \
    -H "x-signature: <computed-signature>"
  ```

  ```javascript Node.js theme={null}
  async function deleteWebhookEndpoint(endpointId) {
    const path = `/v1/webhooks/endpoints/${endpointId}`;
    const headers = generateAuthHeaders('DELETE', path);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'DELETE',
      headers
    });

    return response.status === 204;
  }
  ```

  ```python Python theme={null}
  def delete_webhook_endpoint(endpoint_id: str):
      path = f'/v1/webhooks/endpoints/{endpoint_id}'
      headers = generate_auth_headers('DELETE', path)

      response = requests.delete(f'{BASE_URL}{path}', headers=headers)
      return response.status_code == 204
  ```
</CodeGroup>

***

## Error Codes

| Code                      | HTTP Status | Description                                    |
| ------------------------- | ----------- | ---------------------------------------------- |
| `WEBHOOKS_NOT_CONFIGURED` | 400         | Webhooks feature is not enabled for your group |
| `ENDPOINT_LIMIT_REACHED`  | 400         | Maximum of 16 endpoints per group reached      |
| `INVALID_ENDPOINT_URL`    | 400         | URL must use HTTPS protocol                    |
| `ENDPOINT_PING_FAILED`    | 400         | Validation ping to endpoint URL failed         |
| `INVALID_EVENT_TYPE`      | 400         | Unknown event type in events array             |
| `ENDPOINT_NOT_FOUND`      | 404         | Webhook endpoint does not exist                |

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Receiving Webhooks" icon="bell" href="./webhooks-receiving">
    Handle webhook payloads and verify signatures
  </Card>

  <Card title="HTTP Endpoints" icon="server" href="./endpoints">
    Use webhookMode in payment requests
  </Card>

  <Card title="Authentication" icon="key" href="../authentication">
    HMAC signature computation
  </Card>

  <Card title="Data Types" icon="brackets-curly" href="../reference">
    Webhook data type reference
  </Card>
</CardGroup>
