Approvals
What happens between flagged and paid.
When a request flags
No rule blocks, but at least one returns review — usually the amount is above the auto-approve threshold. Response is HTTP 202, status: "flagged".
How the agent waits
- SDK default:
spend()blocks and polls every 5s for up to 1 hour, then raisesSpendPending. - Non-blocking:
spend(wait=False, webhook_url=…)returns immediately; resume on webhook orpoll(id). - Raw API:
GET poll_urluntil status is notflagged.
Who gets told
| Channel | Who | Setup |
|---|---|---|
| In-app | All Admins and Approvers | None — bell icon, live updates |
| Slack / Teams | Your channel | Settings → paste your own incoming-webhook link (Team plan+) |
Signed decision webhook
After an approver decides, RailGuard POSTs to the request's webhook_url:
Legacy x-ledger-* copies of these headers are also sent for older SDK downloads.
headers
content-type: application/json
x-railguard-timestamp: 1790676000
x-railguard-signature: hex(HMAC_SHA256(key = sha256_hex(api_key), msg = timestamp + "." + raw_body))body
{
"event": "spend.decided",
"transaction_id": "a91e…",
"status": "approved",
"payment_status": "issued",
"comment": "OK for Q3 invoices"
}Retry policy: one delivery attempt. If it fails, an audit row records the failure and the agent must poll. Build your agent to poll as the fallback. Verify with guardrail.verify_webhook(raw_body, timestamp, signature) (rejects older than 5 minutes).
Locked intent
An approval belongs to one transaction: its merchant, amount and rail. The card is issued for that transaction only. Approving $250 does not authorise $300 — a changed amount is a new request that is evaluated and, if flagged, approved again.