# Refund & Dispute Handling

> Refund checkout sessions and invoices in crypto.

Section: Build with AI
Source: https://coincircuit.io/docs/guides/refund-management/
Interactive version: https://coincircuit.io/api-reference?tab=guides&guide=refund-management

Tags: Refunds, Webhooks, Support

Paste this prompt into Claude, ChatGPT, or any AI tool. It works best with the CoinCircuit MCP server connected: https://mcp.coincircuit.io

---

You are helping me add refund handling to my existing CoinCircuit payment integration. I have the CoinCircuit MCP server connected.

**If you have access to the CoinCircuit MCP server, call these tools for the most accurate and detailed schema outputs:**
- Call `search_api` with feature `Refunds` in the CoinCircuit MCP to see all available refund endpoints (estimate, create session refund, create invoice refund, list, get by ID).
- Call `get_endpoint` with method `post` and path `/api/v1/refunds/session/{sessionReference}` for the session refund request/response schema.
- Call `get_endpoint` with method `post` and path `/api/v1/refunds/invoice/{invoiceReference}` for the invoice refund schema.
- Call `get_endpoint` with method `get` and path `/api/v1/refunds/estimate/{reference}` for the refund estimation schema.
- Call `get_schema` with name `RefundCreatedWebhookDto` for the refund.created webhook payload.
- Call `get_schema` with name `RefundSuccessWebhookDto` for the refund.success webhook payload.
- Call `get_schema` with name `RefundFailedWebhookDto` for the refund.failed webhook payload.
- Call `get_schema` with name `RefundResponseDto` for the full refund object shape used in both API responses and webhooks.

Use the live MCP data as your source of truth. The details below are a guide, but if the MCP returns something different, trust the MCP.

## API Basics

- **Base URL:** `https://api.coincircuit.io`
- **Auth:** `x-api-key` header on every request.

## Project Context

I have an application using CoinCircuit for payments and I need to handle customer refund requests for both checkout session payments and invoice payments. CoinCircuit sends crypto refunds to a wallet address you specify.

## What I Need You to Implement

### 1. Estimate refund before processing

Before issuing a refund, show the customer what they'll receive after fees.

**Endpoint:** `GET /api/v1/refunds/estimate/{reference}?entity={type}`
*(Call `get_endpoint` with method `get`, path `/api/v1/refunds/estimate/{reference}` in the CoinCircuit MCP for the full parameter and response details.)*

**Path params:**
- `reference` (string) - the session reference (e.g. `CS_1234567890`) or invoice reference (e.g. `INV_1234567890`)

**Query params:**
- `entity` (required) - `"session"` or `"invoice"`
- `feePaidBy` (optional) - `"merchant"` or `"customer"`

**Response (200):**
- `data.customerReceives` - what the customer gets: `amount`, `asset`, `chain`
- `data.fees.amount` - fee in the refund asset
- `data.fees.asset` - asset the fee is charged in
- `data.fees.fiatAmount` - the same fee in the currency your payments settle into
- `data.fees.fiatCurrency` - currency of `fiatAmount`
- `data.fees.paidBy` - `"customer"` or `"merchant"`
- `data.customerPaid` - the original payment: `cryptoAmount`, `asset`, `fiatAmount`, `fiatCurrency`
- `data.merchantCost` - what is debited from your balance: `amount`, `currency`. Already includes the fee when `paidBy` is `"merchant"`.
- `data.conversionRate` - price of one unit of the refund asset in `merchantCost.currency`
- `data.balanceSource` - `"settled"` or `"pending"`. `"pending"` means the original payment was never added to your balance.

**Example:**
```json
{
  "success": true,
  "message": "Estimation retrieved successfully",
  "data": {
    "customerReceives": { "amount": "0.50098255", "asset": "SOL", "chain": "solana" },
    "fees": {
      "amount": "0.01038637",
      "asset": "SOL",
      "fiatAmount": "1.00",
      "fiatCurrency": "USDT",
      "paidBy": "customer"
    },
    "customerPaid": { "cryptoAmount": "0.52140427", "asset": "SOL", "fiatAmount": "50.00", "fiatCurrency": "USD" },
    "merchantCost": { "amount": "50.00", "currency": "USDT" },
    "conversionRate": "97.77676674533762",
    "balanceSource": "settled"
  }
}
```

Use `customerReceives` and `fees` for a confirmation screen, e.g. "Network fee: 0.01038637 SOL (1.00 USDT), Customer receives: 0.50098255 SOL".

### 2. Refund a checkout session

**Endpoint:** `POST /api/v1/refunds/session/{sessionReference}`
*(Call `get_endpoint` with method `post`, path `/api/v1/refunds/session/{sessionReference}`, section `request` in the CoinCircuit MCP for the exact request body.)*

**Path params:**
- `sessionReference` (string) - the session reference from the original payment

**Request body:**
- `refundAddress` (string, **required**) - the customer's wallet address to receive the refund
- `reason` (string, optional) - e.g. "Customer requested refund", "Product not delivered"
- `feePaidBy` (string, optional) - `"merchant"` or `"customer"`. Defaults to your dashboard settings. Controls who absorbs the blockchain gas fee.

**Example:**
```json
{
  "refundAddress": "TF6yMCJqFcT6wFFutxVmRocKgJFD5imKUT",
  "reason": "Customer requested refund - product not as described",
  "feePaidBy": "merchant"
}
```

**Response (201):** Returns the refund object:
- `data.id` - refund UUID
- `data.entity` - `"session"`
- `data.reference` - the session reference
- `data.status` - `"pending"`, `"processing"`, `"completed"`, or `"failed"`
- `data.chain` - blockchain used for the refund (matches original payment)
- `data.asset` - crypto asset (e.g. `"USDT"`)
- `data.amount` - crypto amount the customer receives (e.g. `"49.50000000"`)
- `data.fee` - network fee in crypto (e.g. `"0.50000000"`)
- `data.feePaidBy` - `"merchant"` or `"customer"`
- `data.balanceSource` - `"settledBalance"` or `"pendingBalance"`
- `data.merchantDebitAmount` - fiat amount debited from your balance
- `data.fiatAmount` / `data.fiatCurrency` - fiat value of the refund
- `data.refundAddress` - customer's wallet
- `data.txHash` - blockchain tx hash (populated on completion)
- `data.explorerUrl` - block explorer link (populated on completion)

*(Call `get_schema` with name `RefundResponseDto` in the CoinCircuit MCP for every field in the refund object, including which are required vs optional.)*

### 3. Refund an invoice

**Endpoint:** `POST /api/v1/refunds/invoice/{invoiceReference}`
*(Call `get_endpoint` with method `post`, path `/api/v1/refunds/invoice/{invoiceReference}` in the CoinCircuit MCP for the full schema.)*

Same request body as session refunds: `refundAddress` (required), `reason` (optional), `feePaidBy` (optional). Same response shape.

### 4. Track refund status via webhooks

*(Call `get_schema` with name `RefundSuccessWebhookDto` in the CoinCircuit MCP for the full refund.success webhook payload. Call `get_schema` with name `RefundFailedWebhookDto` for the failure payload.)*

**Refund events** (envelope: `{ event: string, data: { refund: RefundObject } }`):

- `refund.created` - refund initiated, blockchain transaction pending. `data.refund.status` is `"pending"`. *(Call `get_schema` with name `RefundCreatedWebhookDto` for the full payload.)*
- `refund.success` - crypto sent to customer's wallet. `data.refund.txHash` and `data.refund.explorerUrl` are populated. `data.refund.completedAt` has the timestamp.
- `refund.failed` - refund failed (e.g. invalid refund address). `data.refund.status` is `"failed"`.

**Refund object fields in webhook:**
- `data.refund.id` - refund UUID
- `data.refund.entity` - `"session"` or `"invoice"`
- `data.refund.reference` - original session/invoice reference
- `data.refund.status` - `"pending"`, `"processing"`, `"completed"`, `"failed"`
- `data.refund.amount` - crypto amount customer receives
- `data.refund.fee` - network fee
- `data.refund.feePaidBy` - who paid the fee
- `data.refund.refundAddress` - customer's wallet
- `data.refund.txHash` - blockchain tx hash (on success)
- `data.refund.explorerUrl` - block explorer URL (on success)
- `data.refund.merchantDebitAmount` - fiat amount debited from merchant
- `data.refund.fiatAmount` / `data.refund.fiatCurrency` - fiat value

### 5. Query refund history

*(Call `search_api` with feature `Refunds` in the CoinCircuit MCP to see all listing and detail endpoints.)*

**List all refunds:** `GET /api/v1/refunds` (returns paginated list)
*(Call `get_endpoint` with method `get`, path `/api/v1/refunds` in the CoinCircuit MCP for pagination params and response schema.)*

**Get single refund:** `GET /api/v1/refunds/{id}`
*(Call `get_endpoint` with method `get`, path `/api/v1/refunds/{id}` in the CoinCircuit MCP for the full response.)*

Use these to build a support dashboard showing all refunds with their status, amounts, and transaction details.

### 6. Customer communication flow

Build email/notification triggers based on webhook events:
- On `refund.created`: "Your refund has been initiated. We're sending {amount} {asset} to your wallet."
- On `refund.success`: "Your refund of {amount} {asset} has been sent to {refundAddress}. Track it here: {explorerUrl}"
- On `refund.failed`: "We encountered an issue processing your refund. Our team is looking into it. Reference: {reference}"

## Constraints

- `refundAddress` is **required**. CoinCircuit needs to know where to send the crypto.
- Refunds are async. The API returns immediately with `"pending"` status. Use webhooks for final status.
- A session or invoice can only be refunded **once**.
- You cannot refund more than the original payment amount.
- The refund is sent in crypto (on the same chain/asset as the original payment) regardless of the original fiat denomination.
- Network fees (gas) may be deducted from the refund amount (if `feePaidBy: "customer"`) or from your merchant balance (if `feePaidBy: "merchant"`).
- Use the estimate endpoint first to show the customer what they'll receive.
- Refund statuses: `"pending"` -> `"processing"` -> `"completed"` or `"failed"`.
