Introduction
Stargate is a USDC payment gateway built on Stellar — invoices, hosted checkout, an embeddable widget, and webhook delivery, backed by non-custodial escrow and multi-sig settlement. Think Stripe, but the ledger is Stellar and the asset is USDC.
Merchants create an invoice through the API or dashboard, the payer settles it directly on-chain from their own wallet, and a reconciler confirms the payment and fires a webhook. Stargate never custodies funds mid-flow — payments go straight from payer to a merchant-specific muxed Stellar address.
How it Works
An invoice moves through five stages, each owned by a different service:
| Stage | Owner | What happens |
|---|---|---|
| create | API | Merchant creates an invoice; a muxed Stellar address is derived for it |
| pay | Wallet | Payer fetches unsigned XDR, signs with Freighter/Albedo, submits to Horizon |
| reconcile | Reconciler | Rust sidecar streams Horizon SSE, matches the payment by muxed ID |
| notify | Webhooks | Signed HTTP callback fires to the merchant endpoint |
| settle | Treasury | 2-of-3 multi-sig contract disburses batched settlement |
End-to-end sequence
Merchant ──POST /invoices──▶ API ──▶ Postgres (invoice row + muxed address) Payer ──GET pay/:id────▶ Checkout ──▶ API (prepare-tx) ──▶ Horizon (sign + submit) Horizon ──SSE payments────▶ Reconciler ──▶ Postgres (paid) + Redis (publish) Redis ──message─────────▶ API (SSE) ──▶ Checkout (success screen) Reconciler ──insert delivery─▶ Webhook worker ──▶ Merchant endpoint (HMAC signed)
Creating Invoices
Authenticate with a bearer token, then create an invoice with an amount and expiry:
curl -X POST https://api.stargate.finance/invoices \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{
"amount_usdc": "10.00",
"memo": "Order #1001",
"expires_in_seconds": 900
}'{
"id": "inv_abc123",
"stellar_address": "M...",
"amount_usdc": "10.00",
"status": "pending",
"payment_url": "https://pay.stargate.finance/pay/inv_abc123",
"expires_at": "2026-07-06T00:15:00Z"
}Send payment_url to your customer, or mount the embeddable widget directly on your own checkout page.
Hosted Checkout & Wallets
/pay/[id] is a public hosted checkout page. It fetches the public invoice, screens the payer address, requests payer-specific unsigned XDR from the API, and hands it to the payer's wallet — Stargate never signs or holds funds on the payer's behalf.
Supported wallets
Freighter and Albedo sign the XDR directly in-browser. Mobile wallets (LOBSTR, Solar, Vibrant) scan a SEP-0007 QR code rendered on the checkout page, which encodes aweb+stellar:pay deep link.
Confirmation
Once submitted to Horizon, the checkout page opens a server-sent events stream against /payments/:id/stream. The reconciler publishes to Redis on confirmation, and the API relays that over SSE — no polling required.
Embeddable Widget
Drop a single script tag on your own site to mount a sandboxed checkout iframe, or mount it programmatically for more control.
<script src="https://pay.stargate.finance/widget/stargate-widget.js"
data-invoice-id="inv_abc123"
data-container="#pay-here"></script>window.Stargate.mount({
invoiceId: 'inv_abc123',
container: document.getElementById('checkout'),
origin: 'https://pay.stargate.finance',
onSuccess: ({ invoiceId, txHash }) => { /* order fulfilled */ },
onError: ({ code, message }) => { /* show error */ },
});The iframe emits STARGATE_LOADED, STARGATE_PAID, and STARGATE_ERROR events via postMessage. Always validate event.origin before trusting a message.
Webhooks
Subscribe to only the event types you need:
| Event | Fired when |
|---|---|
| invoice.paid | On-chain payment confirmed by the reconciler |
| invoice.expired | Invoice passes expires_at unpaid |
| invoice.cancelled | Merchant cancels via API |
| settlement.completed | Batch disbursed to merchant treasury |
| merchant.kyc.approved | KYC review approved |
| merchant.kyc.rejected | KYC review rejected |
Every delivery carries X-Stargate-Signature: sha256=<hex>. Verify it before processing the body:
const digest = crypto
.createHmac('sha256', webhookSecret)
.update(rawBody)
.digest('hex');
if (digest !== signatureFromHeader) {
throw new Error('invalid webhook signature');
}Deliveries are queued on Redis Streams with a consumer group, so retries use exponential backoff with jitter and are delivered at least once — build your handler to be idempotent on invoice.id.
Compliance & Settlement
Before any payment XDR is built, the payer address is checked against an on-chain ComplianceContract. A blocked address gets a rejected transaction before it ever reaches Horizon — funds are never in flight to a screened-out address.
Settlement batches merchant balances and disburses them through a TreasuryContract requiring 2-of-3 multi-sig approval, signed via KMS in production.
Security
The hosted checkout and widget enforce strict boundaries so an embedding page can never intercept payment data:
Content-Security-Policy: frame-ancestors https://*.stargate.finance X-Frame-Options: DENY # on /dashboard/* X-Content-Type-Options: nosniff Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Contracts
Three Soroban contracts back the protocol: invoice (escrow state machine), treasury (multi-sig settlement), and compliance (allow/block oracle). ABI metadata is versioned alongside the backend.
FAQ
Does Stargate ever custody funds?
No. Payments go directly from the payer's wallet to a merchant-specific muxed Stellar address. Stargate builds the unsigned transaction and confirms it after the fact — it never holds a signing key for customer funds.
What happens if a webhook delivery fails?
It retries with exponential backoff via a Redis Streams consumer group. Delivery is at-least-once, so handlers should be idempotent.
Can I self-host the widget?
The widget script itself can be self-hosted, but the iframe it mounts always points back at the Stargate checkout origin — that's what keeps signing and compliance screening out of your page's trust boundary.