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
- Register a sandbox endpoint pointing at your local listener (via a tunnel; see below).
- Run your handler locally with the sandbox secret loaded.
- Fire a test_helper to advance a conversion or KYC submission state.
- Receive the webhook and verify it like in production.
- 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:
| Tool | Notes |
|---|---|
cloudflared | Free, automatic HTTPS, no signup. cloudflared tunnel --url http://localhost:3000 |
ngrok | Most popular; HTTPS by default. ngrok http 3000 |
localtunnel | Open-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 $ITo 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 $IOfframp (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 $IDriving 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_SECRETmatches 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
failedwith a connection error, your tunnel URL may have rotated. - Payload looks stale. The
datasnapshot is captured at the moment of the state change. If you need current state, useGET /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
- Signature verification: debug signature mismatches.
- Event catalog: exact payload of every event you'll receive.
