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> --yesWhat you can arm
Pick the row by the symptom you are trying to reproduce.
| Symptom | Row |
|---|---|
| Throttled | error with status 429, plus Retry-After |
| Vendor returned an error | error with a status the twin accepts |
| Slow enough to time out | latency_ms, with no outcome |
| Response never came back | hang |
| Write happened, response lost | hang 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 faultcurl "$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.