Overview
SIQ Labs is a payment orchestration layer. Applications call the SIQ API to create a payment and redirect users to a hosted checkout. SIQ processes the payment through PhonePe and reports the final status via signed webhooks.
- Applications own products, pricing, orders, and customers.
- SIQ owns payment creation, checkout, PhonePe integration, and webhook delivery.
- All amounts are integers in the smallest currency unit (paise for INR).
| Base URL | https://scaleiqlabs.com |
| Hosted Checkout | https://scaleiqlabs.com/pay/{token} |
| Default status page | https://scaleiqlabs.com/pay/status/{payment_id} |
| Documentation | https://scaleiqlabs.com/dev-resources |
Quick Start
- Create an application in the SIQ Labs console.
- Copy the API key and webhook signing secret — shown only once.
- Set your webhook URL and choose environment: TEST or PRODUCTION.
- Call
POST https://scaleiqlabs.com/api/v1/paymentswithamountin paise and a uniqueexternal_reference. - Redirect the customer to the returned
checkout_url. - On webhook delivery, verify the signature and mark your order paid.
Amounts
All amounts are integers in the smallest currency unit. For INR that is paise.
| Human | Send as amount |
|---|---|
| ₹1.00 | 100 |
| ₹49.50 | 4950 |
| ₹999.00 | 99900 |
| ₹4,999.00 | 499900 |
| ₹1,00,000.00 | 10000000 |
Maximum accepted is 10000000 paise (₹1,00,000.00). Non-integer, zero, or negative values are rejected with PAY001.
Authentication
Every API request must include the header:
x-api-key: <Your SIQ API Key>API keys are issued from the SIQ console and shown in plaintext only once. They are stored as SHA-256 hashes on the server. Regenerating a key from the application detail page immediately invalidates the previous key.
An application belongs to exactly one environment. In TEST mode PhonePe is charged ₹1 (100 paise) regardless of the requested amount; both values are returned on every payment resource and webhook payload.
Create Payment
Endpoint: POST https://scaleiqlabs.com/api/v1/payments
Headers: x-api-key, Content-Type: application/json, optional Idempotency-Key (8–200 chars).
Request body:
{
"amount": 99900,
"currency": "INR",
"external_reference": "ORDER-1025",
"success_url": "https://<your-app>/paid",
"failure_url": "https://<your-app>/failed",
"expires_at": "2026-08-01T12:00:00Z",
"metadata": { "order_id": "ORDER-1025" }
}Only amount and external_reference are required. currency defaults to INR. metadata defaults to {}.
Response 201:
{
"payment_id": "…",
"checkout_url": "https://scaleiqlabs.com/pay/…",
"expires_at": null,
"status": "CREATED"
}If the same Idempotency-Key is replayed for the same application, the original payment is returned with "idempotent_replay": true and HTTP 200.
Errors: 401 AUTH001/AUTH002 missing or invalid key · 403 AUTH003/AUTH004 disabled or removed application · 400 PAY001 validation failure · 400 PAY002 malformed Idempotency-Key · 409 PAY003 duplicate idempotency key.
cURL:
curl -X POST https://scaleiqlabs.com/api/v1/payments \
-H "x-api-key: $SIQ_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "content-type: application/json" \
-d '{"amount":99900,"currency":"INR","external_reference":"ORDER-1025"}'Payment Status
Endpoint: GET https://scaleiqlabs.com/api/v1/payments/:payment_id
Headers: x-api-key
Response 200:
{
"payment_id": "…",
"external_reference": "ORDER-1025",
"requested_amount": 99900,
"charged_amount": 100,
"currency": "INR",
"status": "SUCCESS",
"phonepe_transaction_id": "OMO…",
"created_at": "…",
"updated_at": "…"
}Statuses: CREATED · INITIATED · PENDING · SUCCESS · FAILED · EXPIRED · CANCELLED.
Errors: 400 PAY004 invalid id · 404 PAY005 not found for this application.
cURL:
curl https://scaleiqlabs.com/api/v1/payments/<payment_id> \
-H "x-api-key: $SIQ_API_KEY"Hosted Checkout
Redirect the customer to the returned checkout_url of the form https://scaleiqlabs.com/pay/{token}. SIQ renders a branded checkout showing your logo, brand colour, application name, and the amount, then hands off to PhonePe.
The customer may cancel from the checkout screen; that transitions the payment to CANCELLED and triggers the redirect flow below.
The authoritative status update is the webhook — the browser redirect is best-effort.
Redirect Flow
After a terminal outcome, SIQ redirects the browser to your configured URLs and always appends payment_id:
| Outcome | Redirect |
|---|---|
SUCCESS | success_url?payment_id=<payment_id> |
FAILED | failure_url?payment_id=<payment_id> |
CANCELLED | failure_url?payment_id=<payment_id> |
EXPIRED | failure_url?payment_id=<payment_id> |
If a URL is not configured for that outcome, SIQ redirects to its own branded status page:
https://scaleiqlabs.com/pay/status/<payment_id>If your success_url or failure_url already contains query parameters, SIQ appends with &payment_id=… — parse using the standard URL API rather than string-splitting on ?.
The browser redirect is best-effort; the signed webhook is the source of truth.
Payment Links
Payment Links are created from the Admin console. Each link resolves to the same hosted checkout URL https://scaleiqlabs.com/pay/{token} and behaves as follows:
- SUCCESS → link is retired forever.
- FAILED / CANCELLED → link remains usable until expiry.
- Past expiry → link shows the branded Expired page.
Webhook Delivery
SIQ queues a webhook whenever a payment reaches a terminal state — SUCCESS, FAILED, or CANCELLED — and delivers it to the application's configured webhook URL over HTTPS.
| Attempt | Delay from creation |
|---|---|
| 1 | Immediate |
| 2 | +1 minute |
| 3 | +5 minutes |
| 4 | +15 minutes |
| 5 | +1 hour |
| 6 | +6 hours |
Any HTTP 2xx response is treated as delivered. After 6 failed attempts the delivery is marked PERMANENTLY_FAILED. Ordering is guaranteed per payment; across payments deliveries may interleave. Delivery times out after 15 seconds per attempt.
Payload:
{
"payment_id": "…",
"external_reference": "ORDER-1025",
"status": "SUCCESS",
"requested_amount": 99900,
"charged_amount": 100,
"currency": "INR",
"phonepe_transaction_id": "OMO…",
"metadata": { "order_id": "ORDER-1025" },
"created_at": "…",
"updated_at": "…",
"timestamp": "…"
}phonepe_transaction_id is null for CANCELLED payments where PhonePe was never charged.
Webhook Verification
Every webhook carries:
x-siq-signature: <hex hmac-sha256>
x-siq-timestamp: <unix seconds>
User-Agent: SIQ-Labs-Webhook/1.0Signature = HMAC_SHA256(secret, "{timestamp}.{raw_body}")
Node / TypeScript verification:
import { createHmac, timingSafeEqual } from "crypto";
export function verify(rawBody: string, timestamp: string, signature: string, secret: string) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
return a.length === b.length && timingSafeEqual(a, b);
}Idempotency
Send the Idempotency-Key header on POST https://scaleiqlabs.com/api/v1/payments. If the same key is received again for the same application, SIQ returns the original payment instead of creating a new one and the response contains "idempotent_replay": true. Use a UUID per user-initiated payment attempt. Length must be 8–200 characters.
Metadata
The metadata object is stored as-is and echoed back verbatim in every webhook payload and status API response. Use it to link the SIQ payment to your internal order or user record.
Error Codes
| Code | HTTP | Meaning |
|---|---|---|
| AUTH001 | 401 | Missing or malformed x-api-key |
| AUTH002 | 401 | Invalid API key |
| AUTH003 | 403 | Application disabled |
| AUTH004 | 403 | Application removed |
| PAY001 | 400 | Validation failed (see issues[]) |
| PAY002 | 400 | Malformed Idempotency-Key |
| PAY003 | 409 | Duplicate idempotency key |
| PAY004 | 400 | Invalid payment id |
| PAY005 | 404 | Payment not found for this application |
| INTERNAL_ERROR | 500 | Unexpected server error |
Error response shape:
{
"error": { "code": "PAY001", "message": "Validation failed.", "issues": [ { "path": "amount", "message": "..." } ] }
}Testing
In TEST mode PhonePe is charged ₹1 (100 paise) for every payment. requested_amount reflects what you requested; charged_amount is 100. Both values are returned in every webhook payload and status API response so you can build a full test flow without spending real money.
Production Checklist
- Application is in PRODUCTION mode.
- API key generated and stored server-side — never in the browser bundle.
- Webhook signing secret generated and stored server-side.
- Webhook URL configured and reachable over HTTPS.
- Signature verification implemented and timing-safe.
Idempotency-Keysent on every create.- Full payment ↔ webhook round-trip verified in TEST mode.
success_urlandfailure_urlparsepayment_idfrom the query string.
Code Examples
Never call SIQ from the browser. Proxy through your own server so the API key never leaves your infrastructure.
Node 18+ / any server runtime:
const res = await fetch("https://scaleiqlabs.com/api/v1/payments", {
method: "POST",
headers: {
"x-api-key": process.env.SIQ_API_KEY,
"Idempotency-Key": crypto.randomUUID(),
"content-type": "application/json",
},
body: JSON.stringify({
amount: 99900, // ₹999.00 in paise
currency: "INR",
external_reference: "ORDER-1025",
success_url: "https://<your-app>/paid",
failure_url: "https://<your-app>/failed",
metadata: { order_id: "ORDER-1025" },
}),
});
const { checkout_url } = await res.json();
// Return checkout_url to the browser and redirect the customer to it.Reading the redirect on your success / failure page:
const paymentId = new URL(window.location.href).searchParams.get("payment_id");
if (!paymentId) throw new Error("Missing payment_id in redirect URL");
// Fetch the authoritative status from your own server, which calls SIQ:
// GET https://scaleiqlabs.com/api/v1/payments/${paymentId}Using SIQ Labs inside Lovable
SIQ Labs drops into any Lovable project.
- Store
SIQ_API_KEYandSIQ_WEBHOOK_SECRETas project secrets — never in client code. - Create a server function that calls SIQ and returns the
checkout_url. - Redirect the browser to
checkout_url. - Add a public route (e.g.
src/routes/api/public/siq-webhook.ts) that verifies the signature and updates your order.
Server function (TanStack Start):
import { createServerFn } from "@tanstack/react-start";
export const createPayment = createServerFn({ method: "POST" })
.inputValidator((v: { amountPaise: number; orderId: string }) => v)
.handler(async ({ data }) => {
const res = await fetch("https://scaleiqlabs.com/api/v1/payments", {
method: "POST",
headers: {
"x-api-key": process.env.SIQ_API_KEY!,
"Idempotency-Key": crypto.randomUUID(),
"content-type": "application/json",
},
body: JSON.stringify({
amount: data.amountPaise, // integer paise
currency: "INR",
external_reference: data.orderId,
success_url: process.env.APP_URL + "/paid",
failure_url: process.env.APP_URL + "/failed",
metadata: { order_id: data.orderId },
}),
});
if (!res.ok) throw new Error("SIQ create failed: " + res.status);
return res.json(); // { payment_id, checkout_url, expires_at, status }
});Webhook handler:
// src/routes/api/public/siq-webhook.ts
import { createFileRoute } from "@tanstack/react-router";
import { createHmac, timingSafeEqual } from "crypto";
export const Route = createFileRoute("/api/public/siq-webhook")({
server: {
handlers: {
POST: async ({ request }) => {
const signature = request.headers.get("x-siq-signature") ?? "";
const timestamp = request.headers.get("x-siq-timestamp") ?? "";
const rawBody = await request.text();
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return new Response("Stale", { status: 401 });
}
const expected = createHmac("sha256", process.env.SIQ_WEBHOOK_SECRET!)
.update(`${timestamp}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(rawBody);
// event.status ∈ SUCCESS | FAILED | CANCELLED
// event.external_reference is your order id
// Update your order idempotently here.
return new Response("ok");
},
},
},
});Security
- API keys and webhook signing secrets are shown only once — rotate them from the console at any time.
- Verify every webhook signature; use a timing-safe comparison.
- Reject webhook requests older than 5 minutes to defeat replay.
- All traffic must be HTTPS.
- Never place SIQ credentials in client code or version control.
FAQ
Why is charged_amount 100 in TEST mode? TEST applications always charge ₹1 (100 paise) through PhonePe so you can test the full flow without spending real money. requested_amount stays as you sent it.
When do I receive a webhook? When a payment reaches SUCCESS, FAILED, or CANCELLED. SIQ retries delivery up to 6 times.
How do I switch to production? Create a new application in PRODUCTION mode and swap credentials. TEST and PRODUCTION keys are not interchangeable.