> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.useroot.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.useroot.com/_mcp/server.

# Webhooks API

You can receive real-time notifications about platform events by configuring webhook endpoints via APIs. You can create multiple webhook endpoints per entity; each endpoint has its own URL, signing secret, and **event type subscription allowlist** (`event_types`). Only events whose `type` is in that allowlist are delivered to the endpoint (when the endpoint is active). All webhook endpoints must use HTTPS URLs that accept POST requests with JSON payloads. Use the APIs to create, list, get, update (`PATCH`), delete, and toggle activation. Use `GET /api/webhooks/event-types` for the catalog of subscribeable event types when building create or edit forms.

### Event Structure

Every webhook event follows this structure:

``` json
{
  "id": "1vEdMiZ5pmkmrKZfNZ8LeqF2KFP",
  "type": "payout.settled",
  "data": {
      "payout_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

 ```

Example payin event:

``` json
{
  "id": "2wFdNjA6qnlnsLAgOA9MfrG3LGQ",
  "type": "payin.settled",
  "data": {
      "payin_id": "660f9500-f30c-42e5-b827-556766550000"
  }
}

 ```

Example subaccount credit event (`data` includes both `amount_cents` and `amount_in_minor_units`; values match for USD):

``` json
{
  "id": "3xGeOkB7rnmosTbhPB0NgsH4MHR",
  "type": "subaccount.credit_received",
  "data": {
      "subaccount_id": "770a0611-g41d-53f5-b938-667877661111",
      "amount_cents": 50000,
      "amount_in_minor_units": 50000
  }
}

 ```

Each event includes:

- `id`: Unique identifier that remains constant across retries
    
- `type`: Description of the event
    
- `data`: Object containing event-specific information
    

### Available Event Types

The following events are currently available:

**Payout Events:**
- `payout.debited`: Triggered when funds are debited from the sender's account
- `payout.settled`: Triggered when the payout is successfully settled to the recipient
- `payout.failed`: Triggered when a payout fails during processing

**Payin Events:**
- `payin.settled`: Triggered when a payin is successfully settled and funds are received
- `payin.failed`: Triggered when a payin fails during processing

**Subaccount Events:**
- `subaccount.credit_received`: Triggered when an inbound credit is received on a virtual subaccount. The `data` object contains `subaccount_id`, `amount_cents` (legacy), and `amount_in_minor_units` (same value as `amount_cents` for USD today).

Each payout event contains a `data` object with a `payout_id` field containing the UUID of the relevant payout.
Each payin event contains a `data` object with a `payin_id` field containing the UUID of the relevant payin.

### Security and Authentication

To verify webhook authenticity, each payload is signed with a signature passed in the `X-Webhook-Signature` header. This signature is an HMAC SHA-256 hash, using your endpoint's signing secret as the key and the content as:

```
signature_content = "{timestamp}.{payload_str}"

 ```

where the timestamp is included in the header as `X-WEBHOOK-TIMESTAMP`.

**Warning:** Use raw webhook payloads for signature verification. Any modification (such as prettifying or serializing) will result in signature mismatches.

#### Python Signature Validation Example

Here's how to validate webhook signatures in Python:

```python
import hmac
import hashlib

def validate_webhook_signature(payload: str, signature: str, timestamp: str, secret: str) -> bool:
    """
    Validate webhook signature using HMAC SHA-256.
    
    Args:
        payload: Raw webhook payload as string
        signature: Signature from X-Webhook-Signature header
        timestamp: Timestamp from X-Webhook-Timestamp header  
        secret: Your webhook endpoint's signing secret
    
    Returns:
        bool: True if signature is valid, False otherwise
    """
    try:
        # Create signature content
        signature_content = f"{timestamp}.{payload}"
        
        # Calculate expected signature
        expected_signature = hmac.new(
            secret.encode('utf-8'),
            signature_content.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        
        # Compare signatures using constant-time comparison
        return hmac.compare_digest(signature, expected_signature)
        
    except (ValueError, TypeError):
        return False

# Example usage in a Flask/FastAPI endpoint
def webhook_handler(request):
    payload = request.body.decode('utf-8')  # Raw payload as string
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = request.headers.get('X-Webhook-Timestamp')
    secret = "your_webhook_secret_here"
    
    if not validate_webhook_signature(payload, signature, timestamp, secret):
        return {"error": "Invalid signature"}, 401
    
    # Process the validated webhook
    # ... your webhook processing logic here
    
    return {"status": "success"}, 200
```

### Delivery and Retry Mechanism

A successful delivery requires:

- A `2XX` HTTP response within 10 seconds.
    
- Returning the response immediately, then processing the event asynchronously.
    
- Idempotent event processing to handle potential duplicates.
    

### Managing Webhook Security

Protect your endpoint's signing secret. If it is compromised:

1. Create a new endpoint with the same URL and event types.
    
2. Update your system with the new secret.
    
3. Delete the old endpoint.
    

We will continue sending events to the old endpoint until you delete it, after which we will switch to the new one.