BlendFi
Webhooks

Sandbox testing

Drive webhooks against a local listener via test_helpers, debug signature mismatches, exercise every event type without real payments.

In sandbox you can drive every webhook event from a test_helpers endpoint instead of waiting for real Pix or USDT to move. This page covers the loop: register a sandbox endpoint, expose locally, fire test_helpers, watch deliveries arrive.

The loop

  1. Register a sandbox endpoint pointing at your local listener (via a tunnel; see below).
  2. Run your handler locally with the sandbox secret loaded.
  3. Fire a test_helper to advance a conversion or KYC submission state.
  4. Receive the webhook and verify it like in production.
  5. Iterate. Each test_helper fires immediately; deliveries arrive seconds later.

Exposing your local listener

Webhook endpoints must be HTTPS, so localhost on your laptop won't work directly. Use a tunnel:

ToolNotes
cloudflaredFree, automatic HTTPS, no signup. cloudflared tunnel --url http://localhost:3000
ngrokMost popular; HTTPS by default. ngrok http 3000
localtunnelOpen-source alternative. lt --port 3000

Whichever you pick, you get an HTTPS URL like https://something.trycloudflare.com that proxies to localhost. Use that as the url when creating the sandbox endpoint.

# Register a sandbox endpoint pointing at your tunnel
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://something.trycloudflare.com/blendfi-webhooks",
    "event_types": [
      "conversion.created",
      "conversion.completed",
      "conversion.failed",
      "conversion.standby",
      "user.kyc_approved"
    ],
    "description": "local-dev"
  }'

The response includes the plaintext secret: store it as BLENDFI_WEBHOOK_SECRET in your local env before running the handler.

Driving a conversion lifecycle

Each POST against test_helpers/conversions/... advances state and fires the corresponding webhook event.

Onramp (Pix → USDT)

CID=cnv_01J…
H="-H Authorization:Bearer\ $BLENDFI_KEY"
I="-H Idempotency-Key:$(uuidgen)"

# 1. Quote accept creates the conversion (fires conversion.created)
curl -X POST $BLENDFI_BASE/v1/quotes/$QUOTE_ID/accept $H $I

# 2. Simulate the Pix payment → conversion.completed (happy path)
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CID/simulate-onramp-pix-paid $H $I

To exercise failures:

# Window expires without payment → conversion ends in expired
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CID/expire $H $I

# On-chain settlement fails after payment → conversion.failed
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CID/fail-crypto $H $I

Offramp (USDT → Pix)

# 1. Quote accept creates the conversion (fires conversion.created)
curl -X POST $BLENDFI_BASE/v1/quotes/$QUOTE_ID/accept $H $I

# 2. Simulate exact deposit within window → conversion.completed
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CID/deposit $H $I \
  -H "content-type: application/json" \
  -d '{"amount":"100.00"}'

To exercise standby and manual liquidation:

# Lower-amount deposit → conversion.standby (under_funded)
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CID/deposit $H $I \
  -H "content-type: application/json" \
  -d '{"amount":"97.50"}'

# Liquidate manually at the received amount → conversion.completed
curl -X POST $BLENDFI_BASE/v1/conversions/$CID/liquidate $H $I

# Outbound Pix fails → conversion.failed (failure_reason: pix_send_failed)
curl -X POST $BLENDFI_BASE/v1/test_helpers/conversions/$CID/fail-pix $H $I

Driving a KYC lifecycle

KYC test_helpers force the verification verdict:

USER=usr_01J…

# Force approved → fires user.kyc_approved
curl -X POST $BLENDFI_BASE/v1/test_helpers/users/$USER/kyc $H $I \
  -H "content-type: application/json" \
  -d '{"status":"approved"}'

# Force rejected → fires user.kyc_rejected
curl -X POST $BLENDFI_BASE/v1/test_helpers/users/$USER/kyc $H $I \
  -H "content-type: application/json" \
  -d '{"status":"rejected"}'

# Force expired → fires user.kyc_expired
curl -X POST $BLENDFI_BASE/v1/test_helpers/users/$USER/kyc $H $I \
  -H "content-type: application/json" \
  -d '{"status":"expired"}'

Inspecting deliveries

Every delivery (succeeded, failed, in flight) is queryable:

# All deliveries for an endpoint
curl "$BLENDFI_BASE/v1/webhook_endpoints/we_01J.../deliveries" \
  -H "Authorization: Bearer $BLENDFI_KEY"

# A specific delivery
curl $BLENDFI_BASE/v1/webhook_deliveries/del_01J... \
  -H "Authorization: Bearer $BLENDFI_KEY"

Each delivery row carries the response status your endpoint returned, latency, and the last error (if any). When debugging signature mismatches, this is the fastest feedback loop: fire a test_helper, read the deliveries list immediately, see what your endpoint returned.

Common debug recipes

  • Signature mismatch on a test_helper-driven event. Confirm BLENDFI_WEBHOOK_SECRET matches the secret returned at endpoint creation. Double-check the raw body capture before any middleware.
  • Endpoint receives nothing. Check the deliveries list; if BlendFi shows failed with a connection error, your tunnel URL may have rotated.
  • Payload looks stale. The data snapshot is captured at the moment of the state change. If you need current state, use GET /v1/conversions/:id.

No real money in sandbox

Sandbox conversions never touch a real Pix bank or a real exchange. The test_helpers are the only way to advance their state. Events fire bit-for-bit identical to production events for the same transition.

Next steps

On this page