Skip to Content

Rescue Funds

POST/payments/:paymentId/rescue

Description

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

HeaderDescriptionRequired
AuthorizationBearer token with your API keyyes
Content-Typeapplication/jsonyes
X-SignatureSignature from signRescue(), signed with your signing key.yes

Path Parameters

NameTypeDescriptionRequired
paymentIdstringThe unique identifier of the payment to rescue funds from.yes

Request Body

NameTypeDescriptionRequired
tokenstringToken contract address to rescue.yes
receiverstringWallet address to send the rescued tokens to.yes
deadlineintegerUnix 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

SUCCESSFUL RESCUE
{
  "id": "pay_0987654321fedcba",
  "on_chain_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
  "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "receiver": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
  "tx_hash": "0x8a9c67b2d1e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
}

Error Responses

NOTHING TO RESCUE
{
  "error": {
    "type": "invalid_request",
    "message": "No token balance available to rescue"
  }
}
INVALID SIGNATURE
{
  "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".

SCREENING BLOCKED (422)
{
  "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).

Start building

XO

Request Demo

Schedule a call with our team

Select a product
Arrow right

Start building
Grateful

Contact Us

We're here to help