Skip to Content
WorldsFailures and faults

Failures and faults

Arm rate limits, timeouts, 5xx and lost responses as data.

A fault is a row. Insert it to arm the failure, delete it to disarm.

When you need one

A malformed request already fails on its own. Send a bad content type, an unknown field or a nonsense id and you get the vendor’s real error with no setup.

Arm a fault for the other case: a valid request that the vendor answers badly.

Arming one

A fault row goes in a file keyed by twin name, like any other row:

{ "<twin>": { "faults": [ { "method": "POST", "path": "/v1/charges", "outcome": "error", "error": {"status": 429, "headers": {"Retry-After": "2"}}, "remaining": 2 } ] } }
veris sandbox data add fault.json

$CONTROL_URL is the twin’s control URL, printed by veris sandbox services get <twin>.

curl -X POST "$CONTROL_URL/veris/data" \ -H 'Content-Type: application/json' \ -d '{ "data": { "faults": [ { "method": "POST", "path": "/v1/charges", "outcome": "error", "error": {"status": 429, "headers": {"Retry-After": "2"}}, "remaining": 2 } ] } }'

That fires twice, then disarms itself.

path is the vendor path with no scheme, host or query string. A dynamic segment takes a {name} template, /v1/payment_intents/{id}/confirm, which is the only way to arm a fault before the id exists.

match narrows a fault to particular requests by body, query or path field, so only the charge you care about fails.

Read the faults table to see what is armed, and delete a row to disarm it:

veris sandbox data get <twin> faults veris sandbox data delete <twin> faults id=<fault id> --yes

What you can arm

Pick the row by the symptom you are trying to reproduce.

SymptomRow
Throttlederror with status 429, plus Retry-After
Vendor returned an errorerror with a status the twin accepts
Slow enough to time outlatency_ms, with no outcome
Response never came backhang
Write happened, response losthang with "phase": "after"
Gateway returned HTML, not JSON{"status": 502, "raw": true}

phase: after commits the work and then loses the answer. That is the shape behind most duplicate-charge and double-send bugs, and it is difficult to reproduce any other way.

Arm latency_ms deliberately. A twin answers in well under a second where a live vendor’s worst case runs to tens of seconds of backoff, so your timeout and retry paths never run here unless you make them.

422 when the vendor never returns that status

You cannot arm an error the real vendor could not return. The attempt gets a 422 naming the statuses and codes that vendor does accept.

An armed fault returned no error

Your SDK may absorb it. Most vendor clients retry 5xx and connection errors internally, reusing your idempotency key, so an armed 500 can be handled inside a single SDK call and never reach your code. Arm a 429 instead when you want your own retry logic to run.

Check next that your application’s own code path met the fault: the endpoint, worker or job the bug report names, not a curl standing in for it.

Confirming it fired

veris sandbox trace --tier fault
curl "$CONTROL_URL/veris/requests?tier=fault"

That returns the exchange the fault produced. The handler tier returns what your code did next.

Callbacks

Delivery failures arm the same way. Pre-arm a rule, then read the append-only attempt log afterwards to see what was tried and when. See Webhooks.