Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.quantumapi.io/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks deliver real-time notifications to your application when events occur in the Custody API. Every webhook delivery is signed with HMAC-SHA256 so you can verify authenticity.

Registering a webhook endpoint

curl -X POST "$BASE_URL/webhooks" \
  -H "Authorization: Bearer $CUSTODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/webhooks/custody",
    "events": ["transaction.*", "deposit.*"]
  }'
{
  "id": "wh_abc123",
  "url": "https://your-app.example.com/webhooks/custody",
  "events": ["transaction.*", "deposit.*"],
  "secret": "whsec_...",
  "status": "active",
  "created_at": "2026-03-19T10:00:00Z"
}
Save the secret. It is shown only once. You need it to verify webhook signatures.

Event types

Qustody emits 12 webhook event types across four categories.
EventTrigger
transaction.createdNew transaction accepted by the API (state SUBMITTED)
transaction.status_changedAny transition between active states
transaction.completedTransaction reached COMPLETED
transaction.failedTransaction reached FAILED
deposit.detectedInbound transfer detected on a registered wallet
deposit.confirmedInbound transfer confirmed with sufficient depth
approval.requiredPolicy requires manual approval (state PENDING_AUTHORIZATION)
approval.decisionAn approver called approve or reject
screening.submittedCompliance screening was submitted to the provider
screening.completedProvider returned a clean result
screening.flaggedProvider returned a flagged result requiring review
screening.blockedProvider blocked the transaction (state BLOCKED)
Use * wildcards in event subscriptions. For example, transaction.* subscribes to all transaction events; screening.* subscribes to every compliance event.
Pass an empty events array (or omit it) to subscribe to all event types.

Webhook payload format

Every delivery includes these headers:
HeaderDescription
X-Webhook-IDUnique delivery ID
X-Webhook-TimestampUnix timestamp of the delivery attempt
X-Webhook-SignatureHMAC-SHA256 signature for verification
Content-Typeapplication/json
The JSON body follows this structure:
{
  "id": "evt_xyz789",
  "type": "transaction.status_changed",
  "timestamp": "2026-03-19T10:05:00Z",
  "data": {
    "transaction_id": "tx_jkl012",
    "status": "COMPLETED",
    "previous_status": "CONFIRMING"
  }
}

HMAC-SHA256 verification

Always verify the X-Webhook-Signature header to confirm the payload came from Qustody and hasn’t been tampered with. The signature is computed as:
HMAC-SHA256(webhook_secret, timestamp + "." + raw_body)
import hmac
import hashlib

def verify_webhook(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    message = f"{timestamp}.".encode() + body
    expected = hmac.new(
        secret.encode(),
        message,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# In your webhook handler:
# timestamp = request.headers["X-Webhook-Timestamp"]
# signature = request.headers["X-Webhook-Signature"]
# body = request.body (raw bytes)
# verify_webhook(webhook_secret, timestamp, body, signature)
Always use constant-time comparison (hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js, hmac.Equal in Go) to prevent timing attacks.

Retry behavior

Failed deliveries are retried with exponential backoff:
AttemptDelay
1Immediate
230 seconds
32 minutes
410 minutes
51 hour
66 hours
After all retries are exhausted, the event is moved to the dead-letter queue for manual inspection.

What counts as a failure?

  • HTTP response status outside 200–299
  • Connection timeout (10 seconds)
  • DNS resolution failure
  • TLS handshake failure

Managing endpoints

# List all webhook endpoints
curl "$BASE_URL/webhooks" \
  -H "Authorization: Bearer $CUSTODY_API_KEY"

# Delete a webhook endpoint
curl -X DELETE "$BASE_URL/webhooks/{id}" \
  -H "Authorization: Bearer $CUSTODY_API_KEY"

Best practices

Respond quickly

Return 200 OK immediately and process the event asynchronously. Slow responses trigger retries.

Handle duplicates

Use the id field to deduplicate. Retries may deliver the same event multiple times.

Verify signatures

Always validate HMAC-SHA256 before processing. Reject unsigned or invalid deliveries.

Monitor dead letters

Set up alerting on dead-letter events to catch configuration issues early.