Accept your first stablecoin payment.
OpenSettle exposes three independent entry points. The walkthrough below covers the invoice + checkout path; the other two link out to their own short guides.
1. Install the SDK
Typed SDKs ship in Node, Python, Go, and Rust. Or use the REST API directly from any HTTP client.
# Node
npm install @opensettle/sdk
# Python
pip install opensettle
# Go
go get github.com/OpenSettle/opensettle-sdk-go
# Rust
cargo add opensettle2. Create a hosted checkout
One call: name an amount and a settlement rail, and get back a hosted page where the buyer pays with a stablecoin. A one-off charge needs no customer and no pre-created invoice.
import { OpenSettle } from "@opensettle/sdk";
const os = new OpenSettle({
apiKey: process.env.OPENSETTLE_KEY!,
workspaceId: process.env.OPENSETTLE_WORKSPACE!,
testMode: true,
});
// Charge an ad-hoc amount — no customer, no invoice.
const checkout = await os.checkouts.create({
mode: "payment",
amount: 19_900, // minor units — $199.00
currency: "USD", // optional — defaults to USD
chain: "base", // a rail with a verified settlement wallet
token: "USDC",
successUrl: "https://yourapp.com/success",
cancelUrl: "https://yourapp.com/pricing",
});
// Redirect the buyer to the hosted checkout page.
// hostedUrl is already absolute (https://opensettle.io/checkout/...);
// just hand it to Response.redirect verbatim.
return Response.redirect(checkout.hostedUrl, 303);Need line items, a saved customer, or a reusable price? Create an invoice or a price first and pass invoiceId / priceId in place of amount — see Checkouts. First charge on a chain? That rail needs a verified settlement wallet — GET /rails lists the pairs that are ready.
Calling the REST API directly instead of an SDK? Every money-adjacent POST — including checkout creation — requires an Idempotency-Key header (400 without one; replaying the same key returns the original response byte-for-byte). The SDKs generate one per request automatically, which is why it doesn't appear above.
3. Handle the webhook
Verify the HMAC signature, then fulfil the order. The SDK's verifier is constant-time and rejects stale or tampered deliveries.
import { OpenSettle, verifyWebhook, WebhookVerificationError } from "@opensettle/sdk";
const os = new OpenSettle({ apiKey: process.env.OPENSETTLE_KEY! });
app.post("/webhook", async (req, res) => {
try {
const { data } = verifyWebhook<{ id: string; type: string; data: any }>({
rawBody: req.rawBody, // exact bytes — not parsed JSON
signatureHeader: req.header("x-opensettle-signature"),
secret: process.env.WEBHOOK_SIGNING_SECRET!,
});
if (data.type === "payment.confirmed") {
// payment.confirmed carries { paymentId, checkoutId, status, amountMinor,
// chain, token, fullySettled, settledUsdMinor }. Gate fulfilment on
// fullySettled — a dust-band underpayment can auto-credit yet read false.
// GET the payment for customerId + full merchant metadata.
const payment = await os.payments.get(data.data.paymentId);
if (payment.fullySettled) {
await grantAccess(payment.customerId);
await sendReceipt(payment.customerId, payment.id);
}
}
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) {
return res.status(400).end(err.reason);
}
throw err;
}
});4. Drive a payment without real funds
With a test-mode key (sk_test_…), simulatePayment synthesizes a confirmed payment for the checkout and runs the full confirm→settle→webhook fanout — no on-chain transfer — so you can validate your handler end-to-end. Your webhook fires with payment.confirmed exactly as it would in live mode. The endpoint is hard-gated to test-mode workspaces and returns 404 against a live workspace.
const payment = await os.checkouts.simulatePayment(checkout.id);
// Returns the synthesized Payment and delivers a real payment.confirmed
// webhook to your endpoint — inspect the attempt in the dashboard.A real (non-simulated) confirmation still requires a real on-chain transfer to your settlement wallet: open the hosted checkout URL in a wallet, send the asked amount, and your handler fires as soon as the chain-reader observes it (typically within seconds — exact latency depends on the chain).