Rescue Funds
POST/payments/:paymentId/rescueDescription
Recover excess deposits or wrong tokens from a direct payment contract. Transfers the full token balance to the specified receiver. Requires a signed request.
Rescue is typically used when a customer accidentally overpays or sends the wrong stablecoin.
Headers
| Header | Description | Required |
|---|---|---|
| Authorization | Bearer token with your API key | yes |
| Content-Type | application/json | yes |
| X-Signature | Signature from signRescue(), signed with your signing key. | yes |
Path Parameters
| Name | Type | Description | Required |
|---|---|---|---|
| paymentId | string | The unique identifier of the payment to rescue funds from. | yes |
Request Body
| Name | Type | Description | Required |
|---|---|---|---|
| token | string | Token contract address to rescue. | yes |
| receiver | string | Wallet address to send the rescued tokens to. | yes |
| deadline | integer | Unix timestamp after which the signature expires. Use the value returned by signRescue(). | yes |
Example Request
Rescue has no chain-agnostic root helper, so import it from the /evm subpath and pass a per-chain privateKey. If you onboarded with a mnemonic, derive the key with signingKeysFromMnemonic.
import { signingKeysFromMnemonic } from '@exodus/checkout-signer'
import { signRescue } from '@exodus/checkout-signer/evm'
const paymentId = 'pay_0987654321fedcba'
// from the payment webhook:
const onChainId = '0x1a2b3c4d5e...' // on_chain_id
const paymentContractAddress = '0x5fbdb2315678afecb367f032d93f642f64180aa3' // deposit_address
const chain = 'eip155:1' // detected_chain (CAIP-2)
const token = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC on Ethereum
const receiver = '0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21'
const { privateKey } = signingKeysFromMnemonic(process.env.SIGNING_MNEMONIC).evm
const { signature, deadline } = signRescue({
paymentId: onChainId, // SDK field holds the on-chain id, not the pay_... URL id
paymentContractAddress,
token,
receiver,
chain,
privateKey,
})
const response = await fetch(
`https://checkout-api.exodus-int.com/payments/${paymentId}/rescue`,
{
method: 'POST',
headers: {
Authorization: 'Bearer sk_live_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
'X-Signature': signature,
},
body: JSON.stringify({ token, receiver, deadline }),
},
)Response
{
"id": "pay_0987654321fedcba",
"on_chain_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"receiver": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
"tx_hash": "0x8a9c67b2d1e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
}Error Responses
{
"error": {
"type": "invalid_request",
"message": "No token balance available to rescue"
}
}{
"error": {
"type": "authentication_error",
"message": "Invalid signature"
}
}Rescue screens the receiver address before moving funds. If the screen blocks it or the
screening provider is temporarily unavailable, the API returns 422 with error.type: "cannot_process".
{
"error": {
"type": "cannot_process",
"message": "Payment cannot be processed",
"code": "flagged",
"data": {
"payment_id": "pay_0987654321fedcba",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21"
}
}
}error.code is flagged (receiver blocked) or screening_pending (provider temporarily unavailable, retry later).
