Skip to main content

Stablecoin Payment Flow

This guide walks you through the full lifecycle of accepting a customer’s stablecoin payment — from initiation to confirmation and final settlement.

1. Initiate a Payment

Use the POST /payments/initiate endpoint to create a new payment.

Request Body

{
"merchant_id": "merch_xyz",
"amount": "10.00",
"currency": "USDC",
"chain": "best",
"metadata": {
"order_id": "order_123"
}
}
  • merchant_id: Celar-assigned ID for the merchant.
  • amount: Stablecoin amount expected (e.g., 10.00).
  • currency: USDC or USDT.
  • chain: Choose from base, polygon, arbitrum, or use "best" to auto-select.
  • metadata: Optional structured data for reference.

Response

{
"payment_id": "pay_abc123",
"intermediary_wallet": "0x123abc...",
"magic_link": "https://pay.celar.io/pay_abc123",
"status": "pending"
}

2. Customer Sends Funds

Your customer pays the exact amount to the intermediary_wallet provided.

  • The magic link can be shared directly with the customer to show live payment instructions and status.
  • Celar monitors the destination wallet in real time.

3. Receive Confirmation

Once the funds arrive on-chain:

  • Celar emits a payment.confirmed webhook to your registered endpoint.
  • Alternatively, you can poll the status via:
GET /payments/:payment_id
  • The response includes status, amount received, KYT verdict, and more.

4. Settlement

After confirmation:

  • Celar automatically does internal settlement.
  • The settlement is gasless and triggered internally — no action needed from your side.
  • Once complete, a payment.settled webhook is sent with full settlement metadata:
{
"event": "payment.settled",
"payment_id": "pay_abc123",
"psp_amount": "9.50",
"treasury_fee": "0.50",
"tx_hash": "0xabc..."
}

5. Handle Mismatches or Failures

  • payment.mismatched: Sent when overpayment or underpayment is detected.
  • payment.failed: Sent if payment fails for some reason, is blocked by KYT or expires (default: 20 minutes).

To accept payment again after expiry or failure, re-initiate using the POST /payments/initiate endpoint.