Skip to main content
All guides
Advancedsettlementsep-24anchors

Fiat Settlement Configuration

Turn on-chain USDC into local fiat: configure anchors, initiate settlements, and track them to completion.

BettaPay TeamUpdated Jul 19, 202615 min read

Collecting USDC is only half the story — most merchants want the money in their bank account, in local currency. Settlement is the process of converting your on-chain USDC balance to fiat and paying it out through a Stellar anchor using the SEP-24 interactive flow. This guide covers configuring anchors, initiating settlements from the dashboard and API, tracking their lifecycle, handling settlement webhooks, and meeting KYC requirements.

Prerequisite
You should have collected at least one payment (see Accepting Your First Payment) so there's a USDC balance to settle.

How settlement works

BettaPay batches your available USDC and hands it to an anchor. The anchor runs the off-ramp: it verifies your identity (KYC), converts USDC to fiat at its quoted rate, and deposits to your bank account. The SEP-24 standard defines this interactive handoff, and BettaPay tracks the transaction to completion on your behalf.

Supported anchors

Available anchors depend on your settlement country and currency. The table below shows current coverage. Availability changes as anchors complete integration.

AnchorRegionCurrencyRailsStatus
CowrieNigeriaNGNBank transfer (NIP)Available
FonbnkKenyaKESM-Pesa, bankAvailable
Link MoneyGhanaGHSMobile money, bankIn development
MoneyGram AccessGlobalUSDCash pickupIn development
Availability varies
Anchors marked In development appear in the dashboard but can't process live settlements yet. On testnet you'll use the Stellar reference anchor (SEP-24) regardless of the production list above.

Configure settlement in merchant settings

Under Settings → Settlement, configure:

  • Settlement anchor — pick from the anchors available in your region.
  • Payout destination — your bank account or mobile money number. The anchor collects and stores these details during KYC.
  • Schedule — manual (you trigger each settlement) or automatic (settle whenever the balance crosses a threshold).
  • Minimum amount — BettaPay enforces a per-merchant minimum and maximum per settlement; balances below the minimum roll over to the next run.

Initiate a settlement

From the dashboard

Open Settlements → New settlement, review the available balance and the anchor's quoted rate and fee, then confirm. If the anchor needs interactive KYC or a one-time confirmation, a SEP-24 window opens to complete it.

From the API

Send an Idempotency-Key so a retried request never creates a duplicate payout. Amounts are strings to avoid floating-point errors.

create-settlement.js
const { apiRequest } = require("./bettapay-client"); // from the S2S guide

/**
 * Initiate a settlement of USDC to fiat via the configured anchor.
 * Idempotent on `batchRef` so retries are safe.
 */
async function createSettlement({ amount, anchorId, batchRef }) {
  try {
    const settlement = await apiRequest("/v1/settlements", {
      method: "POST",
      idempotencyKey: `settle-${batchRef}`,
      body: {
        amount: String(amount), // USDC to settle
        asset: "USDC",
        anchorId,               // from Settings -> Settlement
        reference: batchRef,    // your own batch id
      },
    });
    // { id, status: "PENDING", amount, fee, quotedRate, anchorId, ... }
    return settlement;
  } catch (err) {
    // Per-merchant limits and insufficient balance surface as 4xx here.
    console.error("settlement failed:", err.message);
    throw err;
  }
}

module.exports = { createSettlement };
Tip
BettaPay enforces per-merchant limits and processes settlements in batches. If you request more than your limit, the API returns a 422 describing the allowed range — split the amount across runs.

Track settlement status

Every settlement moves through a predictable lifecycle:

StatusMeaningTerminal?
PENDINGAccepted and queued for the next batch.No
PROCESSINGHanded to the anchor; KYC/conversion/payout in progress.No
COMPLETEDFiat delivered to your payout destination.Yes
FAILEDAnchor rejected or payout failed; USDC returned to balance.Yes

Fetch the current status any time:

get-settlement.js
const { apiRequest } = require("./bettapay-client");

async function getSettlement(settlementId) {
  const s = await apiRequest(`/v1/settlements/${settlementId}`);
  // { id, status, amount, fee, netFiatAmount, currency, anchorRef, completedAt }
  return s;
}

module.exports = { getSettlement };
Watch out
Prefer webhooks over polling. Settlements can sit in PROCESSING for minutes to hours depending on the anchor and banking rails — polling that whole time is wasteful.

Handle settlement webhooks

BettaPay fires an event on each state change. Verify the signature and dedupe exactly as in Webhook Handling & Retry Logic, then react to the settlement types.

settlement-events.js
function onSettlementEvent(event) {
  switch (event.type) {
    case "settlement.processing":
      markInProgress(event.data.settlementId);
      break;
    case "settlement.completed":
      // Record the NET fiat amount actually delivered, not the gross USDC.
      recordPayout({
        id: event.data.settlementId,
        net: event.data.netFiatAmount,
        currency: event.data.currency,
        anchorRef: event.data.anchorRef,
      });
      break;
    case "settlement.failed":
      // USDC has been returned to your balance; surface the reason.
      alertFinanceTeam(event.data.settlementId, event.data.failureReason);
      break;
    default:
      break;
  }
}

A completed settlement webhook looks like this:

settlement.completed.json
{
  "id": "evt_8b21c0aa",
  "type": "settlement.completed",
  "createdAt": "2026-07-19T14:32:10Z",
  "data": {
    "settlementId": "stl_44f0c9",
    "status": "COMPLETED",
    "amount": "500.00",
    "asset": "USDC",
    "netFiatAmount": "747250.00",
    "currency": "NGN",
    "fee": "2.50",
    "quotedRate": "1502.51",
    "anchorId": "cowrie",
    "anchorRef": "CWR-9F31A",
    "reference": "batch-2026-07-19"
  }
}

Compliance and KYC

Off-ramping to fiat is regulated. Before your first settlement completes, the anchor must verify your identity:

  • Business or individual KYC — legal name, address, and government ID or business registration, collected through the anchor's SEP-24 interactive flow.
  • Bank account verification — the payout destination is validated and, for some anchors, name-matched against your KYC.
  • Limits tiers — higher settlement limits may require additional documentation. Until KYC is approved, settlements stay in PROCESSING or are rejected.
KYC lives with the anchor
BettaPay does not custody funds or store your KYC documents — the anchor does, per its licensing. Complete each anchor's KYC once; afterward, settlements to that anchor process without re-verification.

Troubleshooting

Settlement stuck in PROCESSING

Usually pending KYC or a banking cut-off window. Check the anchor reference (anchorRef) in the settlement detail and, if the anchor requested more documents, complete them in the SEP-24 window.

Settlement FAILED and balance returned

The anchor rejected the payout — commonly a bank detail mismatch or a limit breach. Read failureReason, correct your payout destination in settings, and re-initiate.

"Amount exceeds settlement limit"

You requested more than your per-merchant maximum. Split the amount across multiple settlements, or contact support to raise your tier (which may require additional KYC).

Feature status
Manual settlement, status tracking, and settlement webhooks are available now for anchors marked Available above. Automatic scheduled settlement and additional anchors are in development. On testnet, use the Stellar reference anchor to exercise the full flow.

Next steps