Developer API

Accept payments in your app

Create checkout sessions from your server, send customers a payment link or embedded widget, and receive USDC when they pay via mobile money or Stepay wallet. Webhooks notify your backend when payment succeeds.

Quick start

1. Sign up and open Dashboard → Merchant. Generate an API key — save sk_live_… and whsec_… immediately.

2. Create a checkout from your server:

curl -X POST https://stepay.pro/api/v1/checkouts \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25,
    "asset": "usdc",
    "label": "School fees — Term 2",
    "reference": "STU-1042",
    "success_url": "https://yoursite.com/thank-you",
    "webhook_url": "https://yoursite.com/hooks/stepay"
  }'

3. Redirect the customer to checkout_url from the response, or embed it (see below).

4. Listen for the checkout.paid webhook on your server to mark the order paid.

Authentication

Send your secret API key on every request. Keys start with sk_live_.

Authorization: Bearer sk_live_...
# or
X-Stepay-Key: sk_live_...

Never expose your API key in browser JavaScript. Create checkouts from your backend only.

POST /api/v1/checkouts

Create a one-time payment session.

Request body

{
  "amount": 25,              // required — min 1 USDC or 0.1 XLM
  "asset": "usdc",           // "usdc" (default) or "xlm"
  "label": "Invoice #1042",  // required — shown to payer
  "description": "Optional longer text",
  "reference": "INV-1042",   // your order id (also accepts referenceId)
  "metadata": { "order_id": "1042" },
  "success_url": "https://yoursite.com/done",   // HTTPS only
  "cancel_url": "https://yoursite.com/cancel",  // optional
  "webhook_url": "https://yoursite.com/hooks/stepay",
  "expires_in_minutes": 1440  // 15–10080, default 24h
}

Response

{
  "id": "uuid",
  "checkout_token": "hex32",
  "amount": 25,
  "asset": "usdc",
  "label": "Invoice #1042",
  "status": "pending",
  "expires_at": "2026-07-06T18:00:00.000Z",
  "checkout_url": "https://stepay.pro/pay/checkout/{token}",
  "embed_url": "https://stepay.pro/pay/checkout/{token}?embed=1"
}

GET /api/v1/checkouts/:id

Poll checkout status by UUID.

curl https://stepay.pro/api/v1/checkouts/CHECKOUT_UUID \
  -H "Authorization: Bearer sk_live_..."

Status values: pending, paid, expired, cancelled.

GET /api/v1/checkouts

List your 30 most recent checkouts.

curl https://stepay.pro/api/v1/checkouts \
  -H "Authorization: Bearer sk_live_..."

Webhooks

When a checkout is paid, Stepay POSTs to your webhook_url with event checkout.paid.

Payload

{
  "event": "checkout.paid",
  "checkoutId": "uuid",
  "checkoutToken": "hex32",
  "referenceId": "STU-1042",
  "amount": 25,
  "asset": "usdc",
  "txHash": "stellar-transaction-hash",
  "payerId": "user-uuid-or-null",
  "paymentMethod": "wallet",
  "paidAt": "2026-07-05T18:00:00.000Z",
  "metadata": { "order_id": "1042" }
}

Verify signature (Node.js)

import { createHmac, timingSafeEqual } from 'crypto';

function verifyStepayWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.trim().split('='))
  );
  const timestamp = parts.t;
  const expected = parts.v1;
  if (!timestamp || !expected) return false;

  const signed = `${timestamp}.${rawBody}`;
  const actual = createHmac('sha256', secret).update(signed).digest('hex');
  return timingSafeEqual(Buffer.from(actual), Buffer.from(expected));
}

// Express example:
app.post('/hooks/stepay', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  const sig = req.headers['stepay-signature'];
  if (!verifyStepayWebhook(raw, sig, process.env.STEPAY_WEBHOOK_SECRET)) {
    return res.status(401).send('invalid signature');
  }
  const event = JSON.parse(raw);
  if (event.event === 'checkout.paid') {
    // mark order paid using event.referenceId
  }
  res.sendStatus(200);
});

Header format: Stepay-Signature: t=UNIX_SECONDS,v1=HEX_HMAC. Use the whsec_… secret from the same API key that created the checkout.

Embed checkout

Add an inline payment widget to your site (iframe). Put the container before the script:

<div data-stepay-embed="https://stepay.pro/pay/checkout/CHECKOUT_TOKEN"></div>
<script src="https://stepay.pro/embed.js" defer></script>

Or open checkout in a popup button:

<a href="https://stepay.pro/pay/checkout/TOKEN"
   data-stepay-checkout="https://stepay.pro/pay/checkout/TOKEN"
   class="stepay-pay-btn">Pay with Stepay</a>
<script src="https://stepay.pro/embed.js" defer></script>

Errors

Errors return JSON { "error": "message" } with HTTP status:

  • 401 — missing or invalid API key
  • 400 — invalid amount, label, URL, or expired checkout
  • 404 — checkout not found

Full Node.js example

const API_KEY = process.env.STEPAY_API_KEY;
const BASE = 'https://stepay.pro';

async function createCheckout(order) {
  const res = await fetch(`${BASE}/api/v1/checkouts`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: order.totalUsdc,
      asset: 'usdc',
      label: order.title,
      reference: order.id,
      metadata: { order_id: order.id },
      success_url: `https://shop.example.com/orders/${order.id}/thanks`,
      webhook_url: 'https://shop.example.com/hooks/stepay',
    }),
  });
  if (!res.ok) throw new Error((await res.json()).error);
  return res.json();
}

// Usage:
// const { checkout_url } = await createCheckout({ id: '1042', title: 'Fees', totalUsdc: 25 });
// redirect customer to checkout_url

Need API keys?

Create them in your merchant dashboard after signing up. Test with small amounts first.