Paymaster
Sponsor or accept gas in stablecoins for the customer’s transaction. Read →
Session Payment is the primary way to accept payments through Stablecoin Harness. A session is an invoice: the customer opens a payment link, picks a stablecoin and chain, and sends the exact amount to a deterministic deposit address unique to that session and chain.
Customer wallet ─► Deposit address ─► swap → sUSDe ─► Merchant wallet (CREATE2 Forwarder) (on receipt) (settlement)paymentUrl.(session, chain) gets a unique deposit address — a CREATE2-predicted Forwarder clone.session.confirming webhook fires; if you supplied successUrl, the checkout page redirects the payer there once the webhook is delivered.For L2 chains the funds are swapped to USDe and bridged to a mainnet-path chain via LayerZero V2, where VaultComposerSync stakes into sUSDe for the merchant in the same delivery — surfaced as the bridging status. See L2 Bridge & Vault Compose.
curl -X POST https://api.harness.stablecoinx.com/v1/sessions \ -H "Authorization: Bearer $STABLECOINX_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": "49.99", "merchantName": "Acme Coffee", "lineItems": [ { "name": "Latte (x2)", "quantity": 2, "unitPrice": "5.00", "total": "10.00" }, { "name": "Beans 1kg", "quantity": 1, "unitPrice": "39.99", "total": "39.99" } ], "paymentOptionSymbols": ["USDC", "USDT", "USDe"], "expiresInSec": 1800, "successUrl": "https://example.com/orders/1234/complete" }'Request fields: amount (USD decimal string, required); merchantName?; lineItems?; paymentOptionSymbols? (subset of ["USDC","USDT","USDe"], default all three); expiresInSec? (60 s – 7 days, default 3600); successUrl? (absolute https://); sandboxMode? (boolean — testnet-only when true). There is no currency field and no top-level chain — chains come from the payment options.
Response:
{ "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "paymentUrl": "https://pay.stablecoinx.com/session/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "status": "awaiting_payment", "expiresAt": "2026-05-26T17:30:00Z", "createdAt": "2026-05-26T17:00:00Z"}Send the customer the paymentUrl. To read full details — line items, and the per-chain deposit addresses inside paymentOptions[] — call GET /v1/sessions/:id.
pending → awaiting_payment → confirming → bridging → completed ↘ expired ↘ failed ↘ failed| Status | Meaning |
|---|---|
pending | Created, not yet open for payment. Usually transient. |
awaiting_payment | Open. Deposit addresses are live. |
confirming | On-chain deposit detected; routing through the forwarder + swap. session.confirming webhook fires here. |
bridging | (L2 only) final sUSDe being bridged to mainnet. |
completed | Funds delivered to your wallet as sUSDe. Terminal. |
failed | On-chain execution reverted (slippage, etc.). Terminal. |
expired | Passed expiresAt without a deposit. Terminal. |
No background job actively flips sessions to expired today — the deposit monitor ignores past-expiry sessions, so check expiresAt against the current time on awaiting_payment rows.
Today the API emits a single event type, session.confirming, fired when a session leaves awaiting_payment. Register endpoints from the dashboard or via the webhook management API. Each delivery is signed — see Quickstart → Verify webhook signatures.
Sample delivery:
{ "id": "evt_550e8400-e29b-41d4-a716-446655440000", "type": "session.confirming", "createdAt": "2026-05-26T17:05:23Z", "data": { "session": { "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "merchantName": "Acme Coffee", "amount": "49.99", "status": "confirming", "expiresAt": "2026-05-26T17:30:00Z", "createdAt": "2026-05-26T17:00:00Z", "paidAt": "2026-05-26T17:04:48Z", "txHash": "0xcdf4221a..." } }}Manage endpoints with an sk_live_* key:
| Method | Path | Purpose |
|---|---|---|
POST | /v1/webhooks | Register an endpoint (returns the signing secret once, whsec_*). |
GET | /v1/webhooks · /v1/webhooks/:id | List / inspect. |
PATCH | /v1/webhooks/:id | Update url / events / enabled. |
DELETE | /v1/webhooks/:id | Delete (cascades delivery history). |
POST | /v1/webhooks/:id/rotate-secret | Issue a fresh signing secret. |
POST | /v1/sessions/:id/retry-webhook | Re-dispatch the most recent event for a session. |
Up to 5 endpoints per merchant. Failed deliveries retry on backoff 1m → 5m → 30m → 2h (5 attempts).
Deposit addresses are deterministic — a CREATE2-predicted Forwarder clone per (session, chain), derived from the session UUID and chain ID. The contract is not deployed until funds arrive (ERC-20 balances accumulate at the predicted address). Read the resolved addresses from paymentOptions[].depositAddress on GET /v1/sessions/:id; options on chains without a deployed Forwarder factory are filtered out server-side.
| Chain | Settlement asset |
|---|---|
| Base | sUSDe |
| Arbitrum | sUSDe |
| Ethereum | sUSDe |
Testnets (Base Sepolia, Arbitrum Sepolia, Ethereum Sepolia) are reachable via sandboxMode: true — same code path, testnet stablecoins.