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.
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 match the value used when signing. | yes |
Example Request
import { signRefund } from '@exodus/checkout-signer'
// paymentId: system identifier for the payment (used in the URL path)
const paymentId = 'pay_0987654321fedcba'
// These values come from the payment.escrow_confirmed webhook:
const onChainId =
'0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b' // on_chain_id
const depositAddress = '0x5fbdb2315678afecb367f032d93f642f64180aa3' // deposit_address
const chain = 'eip155:1' // detected_chain (CAIP-2)
const destination = '0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21' // payer_addresses[0]
const deadline = Math.floor(Date.now() / 1000) + 3600 // signature expiry (unix seconds)
const signature = signRefund({
chain,
paymentId: onChainId, // SDK field holds the on-chain id, not the pay_... URL id
depositAddress,
destination,
deadline,
mnemonic: process.env.SIGNING_MNEMONIC,
})
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({ destination, deadline }),
},
)Response
{
"id": "pay_0987654321fedcba",
"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": "authorization_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": "pay_0987654321fedcba",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21"
}
}
}error.code is flagged (destination blocked) or screening_pending (provider temporarily unavailable, retry later).
