Skip to Content

Refund Payment

POST/payments/:paymentId/refund

Description

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

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

Path Parameters

NameTypeDescriptionRequired
paymentIdstringThe unique identifier of the payment to refund.yes

Request Body

NameTypeDescriptionRequired
destinationstringThe wallet address to send the refund to. Must match the address used when signing.yes
deadlineintegerUnix 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

SUCCESSFUL REFUND
{
  "id": "pay_0987654321fedcba",
  "on_chain_id": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b",
  "status": "refunded",
  "destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f8fE21",
  "tx_hash": "0x8a9c67b2d1e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9"
}

Error Responses

INVALID PAYMENT MODE
{
  "error": {
    "type": "invalid_request",
    "message": "Refund via signed request is only available for two-step payments"
  }
}
INVALID SIGNATURE
{
  "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".

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 (destination 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