Refund Payment
POST/payments/:paymentId/refundDescription
Refund a payment, returning funds to the customer’s wallet address. This endpoint is only available for payments created with payment_method: "two_step".
Requires a signed request to authorize the fund movement. You specify the destination address, and it becomes part of your signature.
Your signature is verified before the refund executes. Use signRefund() from the @exodus/checkout-signer SDK. Like every merchant signature, it is EIP-712 typed data.
You receive the customer’s wallet addresses in the payment.escrow_confirmed webhook as payer_addresses. Use payer_addresses[0] as the destination when signing the refund.
Headers
| Header | Description | Required |
|---|---|---|
| Authorization | Bearer token with your API key | yes |
| Content-Type | application/json | yes |
| X-Signature | Signature from signRefund(), signed with your signing key. | yes |
Path Parameters
| Name | Type | Description | Required |
|---|---|---|---|
| paymentId | string | The unique identifier of the payment to refund. | yes |
Request Body
| Name | Type | Description | Required |
|---|---|---|---|
| destination | string | The wallet address to send the refund to. Must match the address used when signing. | yes |
| deadline | integer | Unix timestamp (seconds) after which the signature expires. Must equal the `deadline` returned by `signRefund`. | 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'
// Where the refund goes — e.g. the address that paid
const destination = '0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21' // payer_addresses[0]
// Fetch the payment (or use the payment.escrow_confirmed 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())
// Pass the payment straight in; only the destination is yours to supply
const { signature, body } = signer.signRefund(payment, { destination })
const response = await fetch(
`https://checkout-api.exodus-int.com/payments/${paymentId}/refund`,
{
method: 'POST',
headers: {
Authorization: 'Bearer sk_live_xxxxxxxxxxxxxxxx',
'Content-Type': 'application/json',
'X-Signature': signature,
},
body: JSON.stringify(body),
},
)Response
{
"id": "tz4a98xxat96iws9zmbrgj3a",
"on_chain_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
"status": "refunded",
"destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
"tx_hash": "0x8a9c67b2d1e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
}Error Responses
{
"error": {
"type": "invalid_request",
"message": "Refund via signed request is only available for two-step payments"
}
}{
"error": {
"type": "authentication_error",
"message": "Invalid signature"
}
}Refunds screen the destination 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": "tz4a98xxat96iws9zmbrgj3a",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21"
}
}
}error.code is flagged (destination blocked) or screening_pending (provider temporarily unavailable, retry later).
