Developers
Payment Gateway

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

ModeUse CaseRequired Fields
Ad-hocE-commerce sites with many productsamount + title
Product LinkSelling a specific 1ly productlinkId

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:

FieldTypeRequiredDescription
amountstringYes*Amount in USD (e.g. "49.99")
titlestringNoProduct name shown at checkout
descriptionstringNoProduct description
linkIdstringYes*UUID of a paid link on 1ly
successUrlstringNoRedirect URL after payment
cancelUrlstringNoRedirect URL on cancel
allowedOriginstringNoYour domain for CORS (e.g. "https://mysite.com")
clientReferenceIdstringNoYour order ID for correlation
metadataobjectNoArbitrary 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.

OptionTypeDefaultDescription
checkoutUrlstringrequiredCheckout URL from POST /api/v1/payments
modestring"popup""popup" or "redirect"
onCompletefunctionCalled with payment data on success
onCancelfunctionCalled when buyer cancels
onClosefunctionCalled 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

MeasureHow
Session secrets32-byte random clientSecret, stored as SHA-256 hash
CORSOnly allowedOrigin can poll status
postMessageTargeted to allowedOrigin, never "*"
Redirect validationsuccessUrl/cancelUrl must be valid URLs
Replay protectionTransaction hash uniqueness enforced
IdempotentRe-confirming a confirmed session returns success
Auto-expirySessions expire after 30 minutes

How It Compares

Stripe1ly
CurrencyUSD (fiat)USDC (stablecoin)
Settlement2–7 daysInstant on-chain
Fees2.9% + 30¢5% USDC / 4% $1LY
KYCRequiredWallet-based
ChargebacksYesNo (crypto is final)
GlobalCountry restrictionsAnyone with a wallet