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 URLhttps://scaleiqlabs.com
Hosted Checkouthttps://scaleiqlabs.com/pay/{token}
Default status pagehttps://scaleiqlabs.com/pay/status/{payment_id}
Documentationhttps://scaleiqlabs.com/dev-resources

Quick Start

  1. Create an application in the SIQ Labs console.
  2. Copy the API key and webhook signing secret — shown only once.
  3. Set your webhook URL and choose environment: TEST or PRODUCTION.
  4. Call POST https://scaleiqlabs.com/api/v1/payments with amount in paise and a unique external_reference.
  5. Redirect the customer to the returned checkout_url.
  6. 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.

HumanSend as amount
₹1.00100
₹49.504950
₹999.0099900
₹4,999.00499900
₹1,00,000.0010000000

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:

OutcomeRedirect
SUCCESSsuccess_url?payment_id=<payment_id>
FAILEDfailure_url?payment_id=<payment_id>
CANCELLEDfailure_url?payment_id=<payment_id>
EXPIREDfailure_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.

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.

AttemptDelay from creation
1Immediate
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.0

Signature = 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

CodeHTTPMeaning
AUTH001401Missing or malformed x-api-key
AUTH002401Invalid API key
AUTH003403Application disabled
AUTH004403Application removed
PAY001400Validation failed (see issues[])
PAY002400Malformed Idempotency-Key
PAY003409Duplicate idempotency key
PAY004400Invalid payment id
PAY005404Payment not found for this application
INTERNAL_ERROR500Unexpected 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

  1. Application is in PRODUCTION mode.
  2. API key generated and stored server-side — never in the browser bundle.
  3. Webhook signing secret generated and stored server-side.
  4. Webhook URL configured and reachable over HTTPS.
  5. Signature verification implemented and timing-safe.
  6. Idempotency-Key sent on every create.
  7. Full payment ↔ webhook round-trip verified in TEST mode.
  8. success_url and failure_url parse payment_id from 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.

  1. Store SIQ_API_KEY and SIQ_WEBHOOK_SECRET as project secrets — never in client code.
  2. Create a server function that calls SIQ and returns the checkout_url.
  3. Redirect the browser to checkout_url.
  4. 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.