Skip to Content

Recover Payment

POST/payments/:paymentId/recover

Description

Recover an expired payment back to the customer’s original deposit source. The customer’s address is read from the payment’s recorded payer_addresses — you never supply a destination.

Recovery is EVM-only and requires the payment to have a single recorded source address. Based on the payment status, the server delegates internally:

  • escrow_confirmed — recovered as a refund to the source address.
  • settled — recovered as a rescue of the detected token to the source address.

Requires a signed request to authorize the fund movement.

🔐

Sign with signRefund() when the payment is escrow_confirmed, or signRescue() when it is settled, binding the destination to payer_addresses[0]. Only the deadline is sent in the body — the server derives the destination, token, and amount from the payment. Like every merchant signature, it is EIP-712 typed data.

Headers

HeaderDescriptionRequired
AuthorizationBearer token with your API keyyes
Content-Typeapplication/jsonyes
X-SignatureRefund or rescue signature bound to payer_addresses[0], signed with your signing key.yes

Path Parameters

NameTypeDescriptionRequired
paymentIdstringThe unique identifier of the payment to recover.yes

Request Body

NameTypeDescriptionRequired
deadlineintegerUnix timestamp (seconds) the signature commits to. Must equal the `deadline` returned by the underlying sign call. The destination is derived from the payment, not sent here.yes

Example Request

import { CheckoutSigner } from '@exodus/checkout-signer'
 
const signer = new CheckoutSigner()
 
// paymentId: system identifier for the payment (used in the URL path)
const paymentId = 'tz4a98xxat96iws9zmbrgj3a'
 
// Fetch the payment (or use the payment webhook object as-is)
const payment = await fetch(
  `https://checkout-api.exodus-int.com/payments/${paymentId}`,
  { headers: { Authorization: 'Bearer sk_live_xxxxxxxxxxxxxxxx' } },
).then((r) => r.json())
 
// Recovery returns funds to the recorded source address
const destination = payment.payer_addresses[0]
 
// escrow_confirmed → sign a refund; settled → sign a rescue
const { signature, body } =
  payment.status === 'escrow_confirmed'
    ? signer.signRefund(payment, { destination })
    : signer.signRescue(payment, { token: payment.detected_token_address, receiver: destination })
 
const response = await fetch(
  `https://checkout-api.exodus-int.com/payments/${paymentId}/recover`,
  {
    method: 'POST',
    headers: {
      Authorization: 'Bearer sk_live_xxxxxxxxxxxxxxxx',
      'Content-Type': 'application/json',
      'X-Signature': signature,
    },
    body: JSON.stringify({ deadline: body.deadline }),
  },
)

Response

The response mirrors the underlying operation. A recovered escrow_confirmed payment returns the refund shape; a recovered settled payment returns the rescue shape.

RECOVERED VIA REFUND
{
  "id": "tz4a98xxat96iws9zmbrgj3a",
  "on_chain_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
  "status": "refunded",
  "destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
  "tx_hash": "0x8a9c67b2d1e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
}
RECOVERED VIA RESCUE
{
  "id": "tz4a98xxat96iws9zmbrgj3a",
  "on_chain_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
  "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "receiver": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
  "tx_hash": "0x8a9c67b2d1e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
}

Error Responses

NON-EVM CHAIN
{
  "error": {
    "type": "invalid_request",
    "message": "Recovery is only supported on EVM chains"
  }
}
NOTHING TO RECOVER
{
  "error": {
    "type": "invalid_request",
    "message": "Payment has nothing to recover"
  }
}
AMBIGUOUS SOURCE
{
  "error": {
    "type": "invalid_request",
    "message": "Recovery requires a single recorded source address"
  }
}
INVALID STATUS
{
  "error": {
    "type": "invalid_request",
    "message": "Payment cannot be recovered in status: client_deposit_pending"
  }
}
TOKEN NOT CONFIGURED
{
  "error": {
    "type": "invalid_request",
    "message": "Token DAI is not configured on eip155:1"
  }
}
🛡️

Recovery runs the same compliance screen as the underlying refund or rescue before moving funds. If the screen blocks the destination or the screening provider is temporarily unavailable, the API returns 422 with error.type: "cannot_process".

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