Create payout withdrawal
Auth context: merchant — API key only (JWT not accepted; this is the one
write endpoint in the payout group). Requires cryptoPayoutEnabled=true, a
provisioned webhook signing secret, and at least one active CallbackUrl
registered for the merchant.
Idempotent on
externalId. Retrying the sameexternalIdreturns the original withdrawal unchanged (idempotent replay) — it never double-spends or errors. The1824conflict is only returned on a genuine concurrent duplicate racing the first create.
Withdrawals are per (chain, token), not from a single pool. SkinShark does not hold cross-chain liquidity. You can only withdraw from a chain × token combination you previously deposited to. Depositing USDC on Base and asking to withdraw USDC on Ethereum will fail with
1822 CRYPTO_PAYOUT_INSUFFICIENT_BALANCEeven if you have plenty of USDC on Base. UseGET /payout/crypto/balancesto see what’s withdrawable on each chain.
Flow:
- Live fee computed; rejected with
1821ifmaxFeeUsdCentsis set and the live fee exceeds it. No state mutated. - Atomic transaction: sidecar decremented by
amountCents + liveFeeUsdCents(rejected with1822if insufficient),payout_withdraw_lockledger posting, withdrawal row created inpending_callback. - Synchronous approval call: a signed POST of type
payout.crypto.withdraw.approvalis sent to yourCallbackUrl(5s timeout), separate from the async lifecycle queue. Return 2xx to approve (release funds) or 4xx to reject. The request carrieswebhook-id,webhook-timestamp, andwebhook-signatureheaders — the signature is always present (payouts require a provisioned signing secret), so verify it before acting. - On 2xx →
queued→broadcast→confirmed. On 4xx → immediate refund. On 5xx/timeout/network → retry up to 3 times, then refund. Any rejection, failure, or on-chain revert resolves to the single terminal statusrefunded. - Lifecycle events (
payout.crypto.withdraw.broadcast,.confirmed, and the single terminal.refunded) are delivered asynchronously via the standard signed webhook queue. There is no separate.faileddelivery.
Authorizations
Your raw API key. Generate, rotate, and revoke keys from the merchant
dashboard. Keys can optionally be bound to one or more allowed source
IPs — requests from any other IP are rejected with API_KEY_IP_DENIED.
Body
ethereum, base, arbitrum, optimism, bsc Stables only at MVP.
USDT, USDC EVM address that will receive the funds on-chain.
^0x[a-fA-F0-9]{40}$USD cents to send to the destination. Fee is added on top and debited together.
^\d+$Partner-supplied reference. Echoed in the approval callback and all lifecycle events so
you can match against your own records. Must be unique per merchant; duplicate ⇒ 1824.
1 - 128Optional label — sub-user id (UUID) or your externalId for that sub-user, scoped
to the calling merchant. Pure label, not auth scope: funding always comes from
the merchant's payout custody; the sub-user reference is recorded for audit and
echoed in the approval callback / lifecycle events.
1 - 128Optional fee cap. If the live fee at submission exceeds this cap, the request is
rejected with 1821 and no state is mutated. Useful for declining high-gas moments.
^\d+$