Skip to main content

Payment Intents

A payment intent represents a customer’s intention to pay a specific amount to a merchant. It’s the core object in the Stable Genius API — every payment starts with creating one.

Lifecycle

A payment intent moves through the following statuses:
StatusDescription
awaiting_paymentPayment intent created. QR code displayed. Waiting for customer to send USDC.
confirmedUSDC received and confirmed on-chain. Webhook fired. Fees split.
expiredNo payment received within the TTL window (default: 5 minutes).
cancelledIntegrator cancelled the payment intent before payment was received.
settledFunds have been withdrawn from the merchant’s on-chain wallet and sent to their bank.

Creating a Payment Intent

POST /v1/payment-intents

Required Fields

FieldTypeDescription
amountnumberPayment amount in USD. Minimum: 0.01.Maximum:0.01. Maximum: 10,000.
currencystringAlways "usd" for now.
merchant_idstringThe merchant’s Stable Genius ID (format: mer_*).

Optional Fields

FieldTypeDescription
metadataobjectArbitrary key-value pairs attached to the payment intent. Returned in webhooks. Use for order IDs, terminal IDs, customer references, etc. Max 10 keys, 500 chars per value.
ttlintegerTime-to-live in seconds before the intent expires. Default: 300 (5 minutes). Min: 60. Max: 3600.
idempotency_keystringUnique key to prevent duplicate payment intents. If you send the same key within 24 hours, we return the existing intent instead of creating a new one.

Response Fields

FieldTypeDescription
idstringUnique payment intent ID (format: pi_*).
statusstringCurrent status (see lifecycle above).
amountnumberPayment amount in USD.
payment_addressstringThe on-chain address the customer should send USDC to.
chainstringBlockchain network (base).
tokenstringToken symbol (USDC).
qr_payloadstringEIP-681 formatted URI for QR code rendering. Wallet-compatible.
qr_image_urlstringHosted PNG image of the QR code. 400x400px.
expires_atstringISO 8601 timestamp when the intent expires.
metadataobjectYour attached metadata, returned as-is.
created_atstringISO 8601 timestamp.

Idempotency

Use the idempotency_key field to safely retry requests. If a network error occurs and you’re unsure whether the payment intent was created, resend the same request with the same idempotency key. We’ll return the existing intent if it was already created, or create a new one if it wasn’t.
curl -X POST https://api.stablegenius.co/v1/payment-intents \
  -H "Authorization: Bearer sk_test_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4.50,
    "currency": "usd",
    "merchant_id": "mer_abc123",
    "idempotency_key": "order_456_attempt_1"
  }'
Idempotency keys expire after 24 hours.

Expiration

Payment intents expire after the TTL window (default: 5 minutes). If no USDC is received by expires_at, the status transitions to expired and the payment address is released.
If a customer sends USDC to an expired payment intent’s address, the funds are still received by the merchant’s proxy contract and will be recorded as a transaction — but no payment_intent.confirmed webhook will fire for that specific intent. Use the transaction.created webhook to catch these edge cases.

Metadata

Attach up to 10 key-value pairs to a payment intent. Metadata is not used by Stable Genius — it’s passed through to webhooks so your system can correlate payments with orders, terminals, or customers. Common metadata patterns:
{
  "metadata": {
    "order_id": "order_456",
    "terminal_id": "pos_01",
    "customer_ref": "cust_789",
    "location": "sf_ferry_building"
  }
}