Webhooks API
Webhooks deliver real-time event notifications to your application. When events occur (payments, escrow updates, vault alerts), Subcent sends HTTP POST requests to your registered URLs with signed payloads.
Create Webhook#
/v1/webhooksRegister a new webhook endpoint for a vault.
Request Body#
| Field | Type | Required | Description |
|---|---|---|---|
| vault_id | string | Yes | The vault to receive events for |
| url | string | Yes | The HTTPS URL to receive webhook deliveries |
| events | array | Yes | Array of event types to subscribe to |
Example Request#
{
"vault_id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://your-app.com/webhooks/subcent",
"events": [
"payment.completed",
"payment.pending_approval",
"payment.rejected",
"escrow.funded",
"escrow.released",
"vault.low_balance"
]
}
Response#
{
"webhook_id": "whk_aa0e8400-e29b-41d4-a716-446655440000",
"vault_id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://your-app.com/webhooks/subcent",
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8",
"events": [
"payment.completed",
"payment.pending_approval",
"payment.rejected",
"escrow.funded",
"escrow.released",
"vault.low_balance"
],
"status": "active",
"created_at": "2025-01-15T10:30:00.000Z"
}
The secret field (prefixed with whsec_) is returned only once. Store it securely -- you will need it to verify webhook signatures. It cannot be retrieved again.
List Webhooks#
/v1/webhooksList all webhooks registered for a vault.
Query Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
| vault_id | string | Yes | The vault to list webhooks for |
Response#
[
{
"webhook_id": "whk_aa0e8400-e29b-41d4-a716-446655440000",
"url": "https://your-app.com/webhooks/subcent",
"events": ["payment.completed", "payment.pending_approval"],
"status": "active",
"created_at": "2025-01-15T10:30:00.000Z"
}
]
Delete Webhook#
/v1/webhooks/:idDelete a webhook endpoint.
Path Parameters#
| Parameter | Type | Description |
|---|---|---|
| id | string | The webhook ID |
Response#
{
"deleted": true
}
Errors#
| Error Code | Description |
|---|---|
| webhook_not_found | No webhook exists with the given ID |
Event Types#
Subcent emits the following 11 event types:
Payment Events#
| Event | Description |
|---|---|
| payment.completed | A payment was auto-approved or manually approved and settled on-chain |
| payment.pending_approval | A payment requires human approval before proceeding |
| payment.rejected | A payment was rejected by policy or by human decision |
| payment.failed | A payment failed during on-chain execution |
Escrow Events#
| Event | Description |
|---|---|
| escrow.funded | A new escrow was created and funded |
| escrow.released | Escrow funds were released to the merchant |
| escrow.refunded | Escrow funds were returned to the vault |
| escrow.disputed | A dispute was raised on an escrow |
Vault Events#
| Event | Description |
|---|---|
| vault.low_balance | The vault balance has dropped below a threshold |
| vault.frozen | The vault was frozen |
Policy Events#
| Event | Description |
|---|---|
| policy.updated | A policy was created or updated |
Webhook Payload#
Every webhook delivery is a JSON POST request with the following structure:
{
"event": "payment.completed",
"event_id": "evt_a1b2c3d4e5f6g7h8i9j0k1l2",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"payment_id": "pay_770e8400...",
"status": "completed",
"amount": "25.00",
"currency": "USDC",
"merchant_id": "mrc_660e8400...",
"vault_id": "550e8400...",
"agent_id": "agt_550e8400...",
"transaction": {
"chain": "solana",
"tx_hash": "5UfDuX...",
"finalized_at": "2025-01-15T10:30:02.000Z"
}
}
}
Signature Verification#
Every webhook request includes an X-Subcent-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your webhook secret.
Verification Steps#
- Read the raw request body as a string
- Compute
HMAC-SHA256(webhook_secret, raw_body) - Compare the result with the
X-Subcent-Signatureheader value - Use a constant-time comparison to prevent timing attacks
TypeScript
Retry Behavior#
Subcent retries failed webhook deliveries with exponential backoff:
| Attempt | Delay | Total Wait | |---|---|---| | 1st (initial) | Immediate | 0 seconds | | 2nd (retry 1) | 2 seconds | 2 seconds | | 3rd (retry 2) | 4 seconds | 6 seconds |
After 3 failed attempts, the delivery is marked as failed and no further retries are attempted. A delivery is considered failed if:
- Your endpoint returns a non-2xx HTTP status code
- Your endpoint does not respond within 10 seconds
- The connection cannot be established
Always return a 200 status code quickly from your webhook handler. If you need to do heavy processing, queue the event and process it asynchronously.