Tally supports idempotency keys onDocumentation Index
Fetch the complete documentation index at: https://docs.tallyforagents.com/llms.txt
Use this file to discover all available pages before exploring further.
POST /v1/payments so retries can’t accidentally double-spend. The semantics are simple: same key + same (account, mode) returns the original payment, without re-submitting anything to the chain.
The header… is a body field
Tally takes the idempotency key in the request body, not in anIdempotency-Key header (unlike Stripe). This matches the SDK’s PaymentCreateInput shape:
(account, mode). The same key can be used in test mode and live mode independently, and across different accounts globally.
Semantics
Whenpayments.create (or POST /v1/payments) is called with an idempotency_key:
- Tally looks up an existing transaction in this (account, mode) with the same key.
- If one exists, it returns that record. The body is not revalidated — even if your retry passed a different
tooramount_usdc, you get the original payment back as-written the first time. - If no record exists, the payment proceeds normally. Tally also forwards the key to Privy as
privy-idempotency-keyso the underlying signing call is itself idempotent on Privy’s side.
Choosing keys
Use deterministic strings derived from your own data:- An invoice id:
invoice-2026-04-117 - A workflow run id:
wf-run-7c4f-step-3 - A
(user_id, operation)tuple:user-882-summary-paid-2026-05-18
- Timestamps (
tx-1715990400) — they change on retry, defeating the purpose. - Fresh UUIDs (
tx-${randomUUID()}) — same problem. - The agent id alone (
research-bot) — too coarse; you’ll deduplicate unrelated payments.
When you don’t pass a key
idempotency_key is optional. If you omit it:
- Each retry is treated as a fresh request.
- A network blip after the chain accepts the transaction but before the response reaches you can lead to a duplicate payment when you retry.
When idempotency is the wrong answer
Idempotency keys are scoped to one (account, mode) and one key per intent. Patterns where they’re insufficient:- Splitting a payment into installments. Use distinct keys per installment (
invoice-2026-04-117-installment-1, …). Don’t reuse the parent key. - A payment that legitimately replays a previous one. Example: a user resubscribes, generating a new payment that happens to be identical to last month’s. Use a fresh key — this isn’t a retry, it’s a new operation.
In the SDK
The TypeScript SDK passesidempotency_key through to the body — no special handling required. See SDK payments for the type.