Webhooks API

View as Markdown

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:

1{
2 "id": "1vEdMiZ5pmkmrKZfNZ8LeqF2KFP",
3 "type": "payout.settled",
4 "data": {
5 "payout_id": "550e8400-e29b-41d4-a716-446655440000"
6 }
7}

Example payin event:

1{
2 "id": "2wFdNjA6qnlnsLAgOA9MfrG3LGQ",
3 "type": "payin.settled",
4 "data": {
5 "payin_id": "660f9500-f30c-42e5-b827-556766550000"
6 }
7}

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

1{
2 "id": "3xGeOkB7rnmosTbhPB0NgsH4MHR",
3 "type": "subaccount.credit_received",
4 "data": {
5 "subaccount_id": "770a0611-g41d-53f5-b938-667877661111",
6 "amount_cents": 50000,
7 "amount_in_minor_units": 50000
8 }
9}

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:

1import hmac
2import hashlib
3
4def validate_webhook_signature(payload: str, signature: str, timestamp: str, secret: str) -> bool:
5 """
6 Validate webhook signature using HMAC SHA-256.
7
8 Args:
9 payload: Raw webhook payload as string
10 signature: Signature from X-Webhook-Signature header
11 timestamp: Timestamp from X-Webhook-Timestamp header
12 secret: Your webhook endpoint's signing secret
13
14 Returns:
15 bool: True if signature is valid, False otherwise
16 """
17 try:
18 # Create signature content
19 signature_content = f"{timestamp}.{payload}"
20
21 # Calculate expected signature
22 expected_signature = hmac.new(
23 secret.encode('utf-8'),
24 signature_content.encode('utf-8'),
25 hashlib.sha256
26 ).hexdigest()
27
28 # Compare signatures using constant-time comparison
29 return hmac.compare_digest(signature, expected_signature)
30
31 except (ValueError, TypeError):
32 return False
33
34# Example usage in a Flask/FastAPI endpoint
35def webhook_handler(request):
36 payload = request.body.decode('utf-8') # Raw payload as string
37 signature = request.headers.get('X-Webhook-Signature')
38 timestamp = request.headers.get('X-Webhook-Timestamp')
39 secret = "your_webhook_secret_here"
40
41 if not validate_webhook_signature(payload, signature, timestamp, secret):
42 return {"error": "Invalid signature"}, 401
43
44 # Process the validated webhook
45 # ... your webhook processing logic here
46
47 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.