Event catalog
Every BlendFi webhook event with payload examples, when each fires, and what to do.
The event types BlendFi delivers, grouped by resource: conversion, transaction, and user (KYC). Every payload follows the same shape; only data differs by type. When you register an endpoint you may subscribe to any of the valid event names; the events that actually fire today are the conversion, transaction, and KYC events documented below.
The payload shape
Every webhook delivery POSTs a JSON body in the shape:
{
"id": "evt_01J…",
"type": "conversion.completed",
"created_at": "2026-05-04T13:00:00Z",
"api_version": "2026-05-01",
"data": {
"object": "conversion",
"...": "..."
}
}| Field | Notes |
|---|---|
id | ULID; matches the X-Blendfi-Event-Id header. Use as your dedup key. |
type | One of the event names below. Stable. |
created_at | ISO-8601 UTC. When BlendFi composed the event. |
api_version | Schema version. New fields may be added in a future version; existing fields never change shape. |
data.object | conversion, transaction, or user. |
data.… | The full object as it stood at the state change. |
Conversion events
conversion.created
Fires: conversion created via POST /v1/quotes/:id/accept. The deposit address (offramp) or Pix QR (onramp) is issued; the 15-minute window started.
You usually: show the deposit address or Pix QR to the end customer.
{
"id": "evt_01J...",
"type": "conversion.created",
"created_at": "2026-05-04T13:00:00Z",
"data": {
"object": "conversion",
"id": "cnv_01J...",
"user_id": "usr_01J...",
"transaction_type": "pix_offramp",
"status": "awaiting_deposit",
"expected_source_amount": "100.00",
"deposit_address": "0xabc…",
"deposit_address_network": "polygon",
"deposit_window_expires_at": "2026-05-04T13:15:00Z"
}
}conversion.standby
Fires: conversion entered standby. Offramp only; onramp has no standby. Payload includes standby_reason (one of under_funded, over_funded, window_expired, duplicate_deposit, wrong_address, late_post_window; see Standby).
You usually: decide between POST /v1/conversions/:id/liquidate or wait for manual resolution. See Standby and manual liquidation.
{
"id": "evt_01J...",
"type": "conversion.standby",
"created_at": "2026-05-04T13:05:00Z",
"data": {
"object": "conversion",
"id": "cnv_01J...",
"status": "standby",
"standby_reason": "under_funded",
"expected_source_amount": "100.00",
"received_amount": "97.50",
"standby_at": "2026-05-04T13:05:00Z",
"standby_expires_at": "2026-05-11T13:05:00Z"
}
}conversion.completed
Fires: terminal happy path. Onramp delivered USDT on-chain or offramp delivered Pix to the recipient.
You usually: finalize the partner-visible order; emit your downstream business event.
{
"id": "evt_01J...",
"type": "conversion.completed",
"created_at": "2026-05-04T13:00:20Z",
"data": {
"object": "conversion",
"id": "cnv_01J...",
"status": "completed",
"completed_at": "2026-05-04T13:00:20Z",
"pix_e2e_id": "E12345...",
"usdt_tx_hash": "0xabc…"
}
}conversion.failed
Fires: terminal failure path. failure_reason carries the code.
You usually: branch on failure_reason to decide between user-visible error, retry from a fresh quote, or escalation to support.
{
"id": "evt_01J...",
"type": "conversion.failed",
"created_at": "2026-05-04T13:00:25Z",
"data": {
"object": "conversion",
"id": "cnv_01J...",
"status": "failed",
"failure_reason": "pix_send_failed",
"failure_details": {
"error_code": "upstream_rejected",
"message": "Recipient bank rejected"
}
}
}conversion.expired
Fires: terminal. The 15-minute deposit window passed with no payment (onramp) or no on-chain deposit (offramp). The limit reserve is released.
You usually: mark the partner-visible order as expired; if the end customer still wants to transact, start over with a fresh quote.
{
"id": "evt_01J...",
"type": "conversion.expired",
"created_at": "2026-05-04T13:15:00Z",
"data": {
"object": "conversion",
"id": "cnv_01J...",
"user_id": "usr_01J...",
"quote_id": "qte_01J...",
"transaction_type": "pix_onramp",
"status": "expired"
}
}conversion.canceled
Fires: terminal. You called POST /v1/conversions/:id/cancel while the conversion was in awaiting_deposit. The limit reserve is released.
You usually: the cancel call already returned the canceled conversion to your client. The webhook is the system-of-record copy; use it for downstream dedup, audit, or ops dashboards.
{
"id": "evt_01J...",
"type": "conversion.canceled",
"created_at": "2026-05-04T13:02:00Z",
"data": {
"object": "conversion",
"id": "cnv_01J...",
"user_id": "usr_01J...",
"quote_id": "qte_01J...",
"transaction_type": "pix_offramp",
"status": "canceled",
"expected_source_amount": "100.00"
}
}conversion.abandoned
Fires: offramp only; conversion sat 7 days in standby with no /liquidate. Resolution is manual off-platform.
You usually: open a support ticket with BlendFi citing conversion_id.
{
"id": "evt_01J...",
"type": "conversion.abandoned",
"created_at": "2026-05-11T13:05:00Z",
"data": {
"object": "conversion",
"id": "cnv_01J...",
"status": "abandoned",
"received_amount": "97.50"
}
}No intermediate conversion webhooks
BlendFi does not emit conversion.* events for funded or liquidated: those are transient states that advance automatically. The conversion event of interest is the outcome (completed, failed, standby, expired, canceled, abandoned). For settlement-leg visibility, see the transaction events below.
Transaction events
A transaction is the underlying settlement leg of a conversion: the Pix payout (onramp) or the on-chain transfer. These events give finer-grained visibility into settlement than the conversion lifecycle. They are optional: conversion.* events are the recommended primary signals, and most integrations do not need transaction-level detail. Here data.object is transaction, and these events fire alongside the matching conversion event.
transaction.sending_pix
Fires: onramp settlement started; BlendFi is sending the Pix payout to the recipient.
You usually: nothing. This is informational; wait for conversion.completed.
transaction.completed
Fires: the settlement leg completed. Fires alongside conversion.completed.
You usually: prefer conversion.completed as your trigger; use this only for settlement-level reconciliation.
transaction.failed
Fires: the settlement leg failed. Fires alongside conversion.failed.
You usually: branch on the conversion's failure_reason.
Reserved transaction event names
The set of valid event names accepted when you register an endpoint also includes additional transaction.* names (for example transaction.payment_received, transaction.refunded). They are reserved for future use and are not emitted today: subscribing to them is harmless but they will not fire.
End-customer KYC events
user.kyc_pending
Fires: end customer started a KYC submission.
You usually: show "verification in review" UI.
{
"id": "evt_01J...",
"type": "user.kyc_pending",
"created_at": "2026-05-04T13:00:00Z",
"data": {
"object": "user",
"id": "usr_01J...",
"kyc_status": "pending",
"latest_submission": {
"id": "kyc_01J...",
"submitted_at": "2026-05-04T13:00:00Z"
}
}
}user.kyc_approved
Fires: verification approved the end customer. Cleared to transact.
You usually: unblock the rest of your flow for this end customer.
{
"id": "evt_01J...",
"type": "user.kyc_approved",
"created_at": "2026-05-04T13:05:00Z",
"data": {
"object": "user",
"id": "usr_01J...",
"kyc_status": "approved",
"kyc_approved_at": "2026-05-04T13:05:00Z",
"kyc_expires_at": "2027-05-04T13:05:00Z"
}
}user.kyc_rejected
Fires: verification rejected. The end customer cannot transact.
You usually: show the rejection reason; offer a remediation path or support contact.
{
"id": "evt_01J...",
"type": "user.kyc_rejected",
"created_at": "2026-05-04T13:05:00Z",
"data": {
"object": "user",
"id": "usr_01J...",
"kyc_status": "rejected",
"latest_submission": {
"id": "kyc_01J...",
"rejection_reason": "document_unreadable"
}
}
}user.kyc_expired
Fires: previously approved KYC has lapsed.
You usually: prompt the end customer to reverify; block transactions until they do.
{
"id": "evt_01J...",
"type": "user.kyc_expired",
"created_at": "2027-05-04T13:05:00Z",
"data": {
"object": "user",
"id": "usr_01J...",
"kyc_status": "expired"
}
}Subscribing
Pass the types you want when registering or updating the endpoint:
curl -X POST $BLENDFI_BASE/v1/webhook_endpoints \
-H "Authorization: Bearer $BLENDFI_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{
"url": "https://your-app.example.com/blendfi-webhooks",
"event_types": [
"conversion.created",
"conversion.standby",
"conversion.completed",
"conversion.failed",
"user.kyc_approved",
"user.kyc_rejected"
]
}'The minimum useful subscription for any flow is conversion.completed and conversion.failed. Add conversion.standby if you operate offramp. KYC events are independent of operation flow but valuable for any end-customer-facing app.
New events arrive additively
Subscribing to a new event type later is a no-op for existing conversions and end customers; only future state changes fire the new event.
Next steps
- Signature verification: how to verify these payloads.
- Retries and replay: what happens when your endpoint doesn't respond.
- Sandbox testing: drive any of these events synthetically.
