Recover Payment
POST/payments/:paymentId/recoverDescription
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
| Header | Description | Required |
|---|---|---|
| Authorization | Bearer token with your API key | yes |
| Content-Type | application/json | yes |
| X-Signature | Refund or rescue signature bound to payer_addresses[0], signed with your signing key. | yes |
Path Parameters
| Name | Type | Description | Required |
|---|---|---|---|
| paymentId | string | The unique identifier of the payment to recover. | yes |
Request Body
| Name | Type | Description | Required |
|---|---|---|---|
| deadline | integer | Unix 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.
{
"id": "tz4a98xxat96iws9zmbrgj3a",
"on_chain_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"status": "refunded",
"destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
"tx_hash": "0x8a9c67b2d1e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
}{
"id": "tz4a98xxat96iws9zmbrgj3a",
"on_chain_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"receiver": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
"tx_hash": "0x8a9c67b2d1e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
}Error Responses
{
"error": {
"type": "invalid_request",
"message": "Recovery is only supported on EVM chains"
}
}{
"error": {
"type": "invalid_request",
"message": "Payment has nothing to recover"
}
}{
"error": {
"type": "invalid_request",
"message": "Recovery requires a single recorded source address"
}
}{
"error": {
"type": "invalid_request",
"message": "Payment cannot be recovered in status: client_deposit_pending"
}
}{
"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".
