Never pay twice: retry-safe payouts with externalId

Timeouts happen. With one field, a retried payout returns the original instead of sending the money again. How idempotency works in the Valtu API, including batches.

The Valtu team

Your server sends a payout. The connection drops before the response arrives. Did the payout go through? If your code simply retries, and the first attempt did go through, your customer gets paid twice and your treasury is short.

Valtu solves this with one field: externalId.

How it works

POST /v1/transfers and POST /v1/wallets are idempotent by externalId. Send the same request again with the same externalId and you get the original object back. No second wallet is created, and no second payout is sent.

If you reuse an externalId with different details, say a different amount or destination, the API answers 409 DUPLICATE instead of guessing which one you meant.

const transfer = await valtu.transfers.create({
  walletId: payouts.id,
  asset: "USDC-ERC20",
  amount,
  to: customer.address,
  externalId: `withdrawal_${withdrawal.id}`, // your own id
});

The pattern

  1. Create your payout record first, with its id, and save it.
  2. Call Valtu with that id as externalId.
  3. On a timeout or a 5xx, retry with the same externalId. You'll get either the payout that was already created, or a new one if the first attempt never arrived. Never both.
  4. Track the status by webhook, and reconcile with GET /v1/transfers?after=….

GET requests are always safe to retry. Each attempt needs a fresh timestamp and nonce, because every request is signed.

Batches

POST /v1/transfers/batch takes up to 100 payouts, each with its own required externalId. A batch isn't all-or-nothing: each payout is created or refused on its own, and the response says which. Sending the same batch again after a timeout returns the payouts already created, so the retry is safe here too.

A payout that can't be sent within 30 minutes, because the wallet is short of funds or gas, is cancelled before signing. You won't find a stale payout going out hours later, when you no longer expect it.

More in the API guide: idempotency and retries and batch payouts.

Keep reading

More from the blog

bitcoin · payouts Stuck Bitcoin payouts: how replace-by-fee gets them moving When Bitcoin fees jump after a payout is sent, it can sit unconfirmed for hours. Replace-by-fee resends the same payment with a higher fee, without paying twice. developers · python The Valtu Python SDK is on PyPI pip install valtu: a signed API client, webhook verification and enclave attestation checks for Python 3.9 and later, with the same behavior as the TypeScript wallet kit. networks · developers One address, five networks: how EVM deposit addresses work With Valtu, a customer's 0x address is the same on Ethereum, BNB Chain, Polygon, Base and Lisk. Here is why that works, and what it means for deposits sent on any of them.

Start with a test workspace today.

Same dashboard, same API, free test coins. Go live when your team is ready.