Payment Gateway
Accept USDC payments on any website. Drop-in checkout — like Stripe, but crypto-native.
Works with any framework — React, Next.js, Vue, Shopify, WordPress, or plain HTML.
Quick Start
Get Your API Key
Sign up at 1ly.store (opens in a new tab), go to Dashboard → Settings → Developer, enable Developer Mode, and create an API key.
Add the Widget
<script src="https://1ly.store/widget.js"></script>Create a Payment Session (Server-Side)
Pass any amount and product name directly — no pre-setup needed.
const response = await fetch("https://1ly.store/api/v1/payments", {
method: "POST",
headers: {
"Authorization": "Bearer 1ly_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: "49.99",
title: "Air Max 90 - Size 10",
description: "White/Black colorway",
successUrl: "https://yoursite.com/success",
cancelUrl: "https://yoursite.com/cart",
allowedOrigin: "https://yoursite.com",
clientReferenceId: "order_123",
metadata: { sku: "AM90-WHT-10" },
}),
});
const { data } = await response.json();
// data.checkoutUrl → "https://1ly.store/pay/checkout/..."Open Checkout (Client-Side)
OneLy.open({
checkoutUrl: data.checkoutUrl,
mode: "popup",
onComplete: (result) => {
console.log("Paid!", result.purchaseId, result.txHash);
},
onCancel: () => {
console.log("Canceled");
},
});Two Modes
| Mode | Use Case | Required Fields |
|---|---|---|
| Ad-hoc | E-commerce sites with many products | amount + title |
| Product Link | Selling a specific 1ly product | linkId |
Ad-hoc mode is ideal for e-commerce — you don't need to create products on 1ly. Just pass the amount and title at checkout time.
Product link mode is ideal for fixed products you've already listed on 1ly. Price comes from the link.
API Reference
POST /api/v1/payments
Create a payment session. Returns a checkout URL.
Auth: Authorization: Bearer <api_key>
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
amount | string | Yes* | Amount in USD (e.g. "49.99") |
title | string | No | Product name shown at checkout |
description | string | No | Product description |
linkId | string | Yes* | UUID of a paid link on 1ly |
successUrl | string | No | Redirect URL after payment |
cancelUrl | string | No | Redirect URL on cancel |
allowedOrigin | string | No | Your domain for CORS (e.g. "https://mysite.com") |
clientReferenceId | string | No | Your order ID for correlation |
metadata | object | No | Arbitrary data stored with the session |
*Either amount or linkId is required. If both provided, linkId takes precedence.
Response:
{
"success": true,
"data": {
"id": "session-uuid",
"clientSecret": "pscs_...",
"status": "open",
"amount": "49.99",
"currency": "USDC",
"expiresAt": "2026-02-08T12:30:00Z",
"checkoutUrl": "https://1ly.store/pay/checkout/SESSION_ID?cs=SECRET",
"item": {
"title": "Air Max 90 - Size 10",
"description": "White/Black colorway",
"amount": "49.99"
},
"merchant": { "username": "mystore" }
}
}GET /api/v1/payments/:id
Check payment status. Requires cs (client secret) as query parameter.
GET /api/v1/payments/:id?cs=pscs_...Response:
{
"success": true,
"data": {
"id": "session-uuid",
"status": "open",
"amount": "49.99",
"currency": "USDC",
"purchaseId": null,
"txHash": null,
"chain": null,
"buyerWallet": null,
"link": null,
"merchant": { "username": "mystore", "displayName": "My Store" }
}
}Status values: open, confirmed, expired, canceled, failed
This endpoint supports CORS from the allowedOrigin you set when creating the session. The widget uses it for status polling.
Widget Reference
OneLy.open(options)
Open the 1ly checkout.
| Option | Type | Default | Description |
|---|---|---|---|
checkoutUrl | string | required | Checkout URL from POST /api/v1/payments |
mode | string | "popup" | "popup" or "redirect" |
onComplete | function | — | Called with payment data on success |
onCancel | function | — | Called when buyer cancels |
onClose | function | — | Called when popup closes (any reason) |
onComplete callback data:
{
sessionId: "uuid",
purchaseId: "uuid",
txHash: "0x...",
chain: "solana" | "base",
amount: "49.99",
clientReferenceId: "order_123"
}OneLy.close()
Close the active checkout popup.
Full Integration Example
Node.js / Express
const express = require("express");
const app = express();
const ONELY_API_KEY = process.env.ONELY_API_KEY;
// Create checkout session
app.post("/api/checkout", async (req, res) => {
const { cartId, total, productName } = req.body;
const session = await fetch("https://1ly.store/api/v1/payments", {
method: "POST",
headers: {
"Authorization": `Bearer ${ONELY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: total.toString(),
title: productName,
successUrl: `https://myshop.com/orders/${cartId}/confirmed`,
cancelUrl: "https://myshop.com/cart",
allowedOrigin: "https://myshop.com",
clientReferenceId: cartId,
}),
}).then((r) => r.json());
res.json({ checkoutUrl: session.data.checkoutUrl });
});
// Receive webhook
app.post("/webhooks/1ly", (req, res) => {
const { event, purchaseId, txHash, amount } = req.body;
if (event === "purchase.confirmed") {
// Mark order as paid in your database
// Ship product, send email, etc.
}
res.sendStatus(200);
});Frontend (any framework)
<script src="https://1ly.store/widget.js"></script>
<button id="pay-btn">Pay with USDC</button>
<script>
document.getElementById("pay-btn").addEventListener("click", async () => {
const { checkoutUrl } = await fetch("/api/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
cartId: "cart_abc",
total: 49.99,
productName: "Air Max 90",
}),
}).then((r) => r.json());
OneLy.open({
checkoutUrl,
onComplete: (data) => {
window.location.href = "/order/confirmed?tx=" + data.txHash;
},
onCancel: () => {
console.log("Payment canceled");
},
});
});
</script>Webhooks
When a payment completes, 1ly sends a purchase.confirmed webhook to the link's configured webhook URL. See the Webhooks documentation for details on signature verification.
Security
| Measure | How |
|---|---|
| Session secrets | 32-byte random clientSecret, stored as SHA-256 hash |
| CORS | Only allowedOrigin can poll status |
| postMessage | Targeted to allowedOrigin, never "*" |
| Redirect validation | successUrl/cancelUrl must be valid URLs |
| Replay protection | Transaction hash uniqueness enforced |
| Idempotent | Re-confirming a confirmed session returns success |
| Auto-expiry | Sessions expire after 30 minutes |
How It Compares
| Stripe | 1ly | |
|---|---|---|
| Currency | USD (fiat) | USDC (stablecoin) |
| Settlement | 2–7 days | Instant on-chain |
| Fees | 2.9% + 30¢ | 5% USDC / 4% $1LY |
| KYC | Required | Wallet-based |
| Chargebacks | Yes | No (crypto is final) |
| Global | Country restrictions | Anyone with a wallet |