Developers
Quickstart for accepting payments with the LevinGate API.
Quickstart in your language
Node.js
18+ · built-in fetch
docs/developers/nodejs.md
Python
requests
docs/developers/python.md
.NET (C#)
.NET 8 · HttpClient
docs/developers/dotnet.md
Go
1.22+ · net/http
docs/developers/go.md
Each guide covers create → retrieve → list → webhook verification → errors, end to end.
1. Create an invoice
Amounts are decimal strings — never floating point. Tokens are identified by contract address on the backend; you send asset + network. Send an Idempotency-Key so retries never double-create.
curl -X POST https://api.levingate.example/api/v1/invoices \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_12345" \
-d '{
"amount": "25.00",
"currency": "USD",
"asset": "BNB",
"network": "BSC",
"external_order_id": "order_12345",
"customer_email": "customer@example.com"
}'Interactive reference: Swagger UI at /api/docs
2. Endpoint map
| POST | /invoices | create (Idempotency-Key respected) |
| GET | /invoices/:id | status + checkout_url |
| GET | /invoices | list · cursor pagination |
| GET | /payments/:id | on-chain payment detail |
| GET | /transactions | platform tx list · env-scoped |
| GET | /networks | active networks (current env) |
| GET | /assets | active assets (current env) |
| GET | /stats | totals + daily series (?days=1..90) |
| POST | /webhooks | dashboard sessions only — 403 with API keys (by design) |
3. Listing & errors
# Cursor pagination — follow next_cursor while has_more is true
curl "https://api.levingate.example/api/v1/invoices?limit=50" \
-H "Authorization: Bearer sk_test_your_api_key"
# Errors always look like:
# { "error": { "code": "INVALID_REQUEST", "message": "…", "request_id": "req_…" } }4. Receive webhooks
Configure your endpoint under Webhooks in the sidebar (dashboard-only — API keys cannot manage webhook endpoints by design). Every delivery is signed with HMAC-SHA256 over timestamp + '.' + raw_body. Always verify against the raw request body, before JSON parsing.
import { createHmac, timingSafeEqual } from "node:crypto";
/**
* Verify a LevinGate webhook (§28).
* Header: X-LevinPay-Signature: t=<timestamp>,v1=<hmac_sha256(secret, ts + "." + raw_body)>
*/
export function verifyWebhook(rawBody: string, header: string, secret: string) {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.trim().split("=")),
);
const { t, v1 } = parts;
if (!t || !v1) throw new Error("Malformed signature header");
// Reject replays older than 5 minutes
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) {
throw new Error("Timestamp outside tolerance window");
}
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
if (
expected.length !== v1.length ||
!timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
) {
throw new Error("Signature mismatch");
}
return JSON.parse(rawBody);
}A ready-made verifier is also available in @levinpay/sdk.
Good to know
Invoices are marked PAID only after the configured number of confirmations.
Payments arriving after expiry are recorded with is_late and routed to reconciliation — the invoice stays EXPIRED.
LevinGate is non-custodial: funds go straight to your own wallet. We never hold keys or sign transactions.