# SaaS Invoice Billing

> Bill subscriptions with crypto invoices, expiry timers, and partial payments.

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

Tags: Invoices, Webhooks

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 integrate CoinCircuit crypto invoicing into my existing SaaS billing system. 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 `get_endpoint` with method `post` and path `/api/v1/invoices` in the CoinCircuit MCP for the full invoice creation schema.
- Call `search_api` with query `invoice webhook` and type `schemas` to list all invoice webhook payload schemas.
- Call `get_schema` with name `InvoicePaidWebhookDto` for the exact invoice.paid webhook payload.
- Call `get_schema` with name `InvoiceExpiredWebhookDto` for the invoice.expired webhook payload.

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 a SaaS app that bills customers monthly. I'm adding crypto invoice payments via CoinCircuit so customers can pay in any supported crypto (USDT, BTC, ETH, etc.).

## What I Need You to Implement

### 1. Invoice creation on subscription renewal

**Endpoint:** `POST /api/v1/invoices`
*(Call `get_endpoint` with method `post`, path `/api/v1/invoices`, section `request` in the CoinCircuit MCP for the exact request body schema with all field types and validations.)*

**Required fields:**
- `amount` (string) - the subscription price as a string with up to 2 decimal places, e.g. `"29.99"`
- `currency` (string) - `"NGN"` or `"USD"`
- `description` (string) - e.g. "Pro Plan - May 2026"
- `expiresAt` (string, ISO 8601) - when the invoice expires. Max 2 days from creation. Defaults to 12 hours if omitted.
- `customer` (object) - must include `email` (required). Optional: `firstName`, `lastName`, `phone` (E.164), `telegramId`

**Optional fields:**
- `reference` (string) - custom invoice reference, e.g. `"INV-2026-005"`. Auto-generated if omitted.
- `periodStart` / `periodEnd` (string, ISO 8601) - billing period dates for subscription context
- `nextPaymentDate` (string, ISO 8601) - next billing date, shown on the invoice
- `metadata` (object) - store `subscriptionId`, `plan`, etc. for webhook reconciliation
- `successUrl` / `cancelUrl` (string) - redirect URLs after payment
- `asset` (string) - lock to a specific crypto (`"BTC"`, `"ETH"`, `"USDT"`, `"USDC"`, `"SOL"`, `"BNB"`, `"TRX"`)
- `chain` (string) - lock to a specific blockchain (`"bitcoin"`, `"ethereum"`, `"solana"`, `"bsc"`, `"tron"`, `"base"`, `"arbitrum"`)

**Example request body:**
```json
{
  "amount": "29.99",
  "currency": "USD",
  "description": "Pro Plan - May 2026",
  "expiresAt": "2026-05-03T00:00:00.000Z",
  "customer": {
    "email": "subscriber@example.com",
    "firstName": "John",
    "lastName": "Doe"
  },
  "periodStart": "2026-05-01T00:00:00.000Z",
  "periodEnd": "2026-05-31T23:59:59.000Z",
  "nextPaymentDate": "2026-06-01T00:00:00.000Z",
  "metadata": {
    "subscriptionId": "sub_abc123",
    "plan": "pro"
  },
  "successUrl": "https://myapp.com/billing/success"
}
```

**Response (201):** Returns the invoice object. Key fields:
- `data.id` - invoice UUID
- `data.reference` - invoice reference (e.g. `"inv_ref_abc123xyz789"`)
- `data.url` - payment URL (e.g. `https://checkout.coincircuit.io/invoice/inv-abc123`)
- `data.status` - starts as `"pending"`, transitions to `"partial"`, `"paid"`, or `"expired"`
- `data.state` - `"open"` (accepting payments) or `"closed"` (finalized)
- `data.amount` / `data.amountPaid` - track payment progress
- `data.isRefunded` - boolean

*(Call `get_endpoint` with method `post`, path `/api/v1/invoices`, section `success` in the CoinCircuit MCP for the full response schema.)*

**Error responses:** 400 (invalid input or expiresAt exceeds 2 days), 401 (bad API key)

### 2. Send the payment link

The response includes `data.url`. Email this to the customer or display it in your app's billing dashboard. The hosted page handles crypto selection, address display, and payment detection automatically.

### 3. Webhook event handling

*(Call `get_schema` with name `InvoicePaidWebhookDto` in the CoinCircuit MCP for the full invoice.paid webhook payload with all nested fields including the payments array.)*

**Invoice events** (envelope: `{ event: string, data: { invoice: InvoiceObject, payments?: PaymentArray } }`):

- `invoice.created` - confirmation the invoice was created. Good for logging. *(Call `get_schema` with name `InvoiceCreatedWebhookDto` for details.)*
- `invoice.paid` - full payment received. **Activate or extend the subscription.** The `data.payments` array contains details on each payment session: `asset`, `chain`, `amount`, `address`, `txHash`, `explorerUrl`, `status`.
- `invoice.updated` - partial payment received. Check `data.invoice.amountPaid` vs `data.invoice.amount` to track progress. `data.invoice.status` will be `"partial"`. *(Call `get_schema` with name `InvoiceUpdatedWebhookDto` for details.)*
- `invoice.expired` - invoice expired unpaid. Suspend access or create a new invoice with a reminder email. *(Call `get_schema` with name `InvoiceExpiredWebhookDto` for details.)*

**Invoice webhook data fields:**
- `data.invoice.reference` - matches your invoice
- `data.invoice.metadata` - your `subscriptionId` and `plan` are here
- `data.invoice.amount` / `data.invoice.amountPaid` - track cumulative payments
- `data.invoice.status` - `"pending"`, `"partial"`, `"paid"`, `"expired"`
- `data.invoice.paidAt` - ISO 8601 timestamp when fully paid
- `data.invoice.periodStart` / `data.invoice.periodEnd` - billing period
- `data.invoice.customer.email` - for confirmation emails

**Webhook security:** Same as checkout: verify HMAC-SHA256 via `x-coincircuit-signature`, deduplicate via `x-coincircuit-delivery-id`.

## Constraints

- Invoice `amount` is a **string** with up to 2 decimal places (e.g. `"29.99"`), same as checkout sessions
- `expiresAt` max is 2 days from creation. Defaults to 12 hours.
- Invoices support partial payments. Track `amountPaid` before activating the subscription.
- The invoice stays `"open"` until fully paid or expired
- Supported currencies: NGN, USD
