BlendFi
Webhooks

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",
    "...": "..."
  }
}
FieldNotes
idULID; matches the X-Blendfi-Event-Id header. Use as your dedup key.
typeOne of the event names below. Stable.
created_atISO-8601 UTC. When BlendFi composed the event.
api_versionSchema version. New fields may be added in a future version; existing fields never change shape.
data.objectconversion, 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

On this page