Merchant API Reference

Version 1 · Base URL: https://payments.alvanlabs.com/api/v1

The Alvan Labs Pay API enables merchants to accept UPI payments with fully automatic reconciliation — no payment gateway required. When a customer initiates a payment, the API assigns a system-determined payment amount using proprietary reconciliation logic, enabling zero-touch matching of incoming credits to orders.

🔒 No Credentials Required

API calls only require your merchant_id. No OAuth tokens, no API keys in headers.

⚡ Real-Time Verification

Payments are verified in real-time via instant email monitoring. Orders are typically verified within 1–3 seconds of payment — no manual checks needed.

🔁 Auto-Reconciliation

Bank credit emails are parsed and matched to orders automatically — no manual reconciliation needed.

Getting Started

Everything you need to set up before going live

What you need to provide

📱 UPI ID

Your UPI ID is the payment address where customers send money — e.g. yourname@okaxis, 9876543210@ybl.

✅ A personal UPI ID works perfectly. You do not need a dedicated merchant or business UPI ID. Any UPI ID linked to your bank account is supported.

📧 Gmail Address

The Gmail account that receives bank credit notifications when a UPI payment is credited to your account.

This must be the Gmail address registered with your bank for email alerts. Most banks send a credit email within 30–90 seconds of a payment.

🔑 Gmail OAuth2 (One-click Setup)

A special read-only access token so our system can read your bank credit emails. This is not your Gmail login password — it is a separate 16-character app-specific password generated inside your Google account.

See the step-by-step guide below.

🏦 Bank Sender Email

The exact email address your bank uses to send credit notification emails — e.g. alerts@hdfcbank.net, credit@axisbank.com.

This lets us filter only genuine bank credit emails from your inbox.

How to connect Gmail via OAuth2

Takes about 1 minute. Uses Google's official OAuth2 — no passwords shared.

1

Log in to your Merchant Dashboard

Go to payments.alvanlabs.com/dashboard/settings

2

Click "Connect Gmail"

In the Gmail & Bank Alerts section, click the Connect Gmail → button. You will be redirected to Google's official sign-in page.

3

Authorize read-only access

Select the Gmail account that receives your bank credit alerts. Grant the read-only permission when prompted by Google.

4

Set your Bank Sender Email

Back in Settings, enter the email address your bank uses to send credit alerts (e.g. alerts@hdfcbank.net). This filters out all non-bank emails.

Done — instant monitoring is now active

Your dashboard will show ✅ Gmail API — Real-time Push Active. Payments will now be verified within seconds of the bank credit email arriving.

How to find your Bank Sender Email

Open your Gmail and search for a recent UPI credit notification from your bank. The "From" address is what you need. Common examples:

Bank Typical sender email
HDFC Bankalerts@hdfcbank.net
ICICI Bankinfo@icicibank.com
Axis Banknoreply@axisbank.com
SBIsbialerts@alerts.sbi.co.in
Kotakalerts@kotak.com
IndusIndalerts@indusind.com
Yes Bankstatements@yesbank.in

If your bank is not listed, simply open any recent UPI credit email and check the "From" field.

🔒

Your Privacy & Security

  • Email access is read-only. Gmail OAuth2 grants read-only access to your inbox. It cannot send, delete, or modify anything.
  • OAuth2 — no password shared. We never see your Gmail password. Google issues a secure access token that can be revoked at any time from your Google Account.
  • Only bank sender emails are read. Our parser filters strictly by the bank sender email you configure. Emails from any other sender are completely ignored.
  • No email content is stored. We extract only the credited amount and UTR — no email body, subject, or other content is persisted.
  • You can revoke access anytime. Disconnect from your dashboard Settings page, or remove access directly at myaccount.google.com/permissions. This immediately terminates our access.
  • Credentials are never shared. Your details are isolated per merchant account and are never shared with other merchants or third parties.

Questions? Email us at payments@alvanlabs.com

Authentication

How to identify yourself as a merchant

All API requests are authenticated using your Merchant Key — a unique string in the format mch_<24 hex chars>. This key is passed as a request body field or query parameter depending on the endpoint. There are no bearer tokens or API key headers required.

⚠️ Security Note: Your Merchant Key uniquely identifies all transactions under your account. Do not expose it in client-side JavaScript or public repositories. For WooCommerce, store it in the plugin settings only — it is sent from your server to ours, never directly from the browser.

Where to find your Merchant Key:

  1. Log in to your merchant dashboard at payments.alvanlabs.com/dashboard
  2. Go to Settings — your Merchant Key is shown there
  3. Click the copy icon (📋) next to the key
# Example Merchant Key format
merchant_id = "mch_4b7d2e9f1a3c058762de4f91"

Error Handling

Standard HTTP status codes and error response format

All errors return a JSON body with a detail field describing the issue.

HTTP Status Meaning Example detail
400Bad Request — malformed JSON or missing fieldvalue is not a valid string
404Merchant not found (invalid merchant_id)Merchant not found.
429Rate limit exceeded — too many checkout requests from the same IP within 1 minute, or monthly plan order limit reachedToo many checkout requests. Please try again in a minute.
503No payment sessions available — all 99 cent slots in use, try again shortlyAll payment slots full.
500Internal server errorPayment session creation failed.
// Error response body
{
  "detail": "Merchant not found."
}

End-to-End Payment Flow

How a payment goes from initiation to verified

1

Merchant calls /api/v1/initiate-pay

Your server sends order details + merchant_id. The API creates a payment session with a system-assigned amount and returns a UPI deep link.

2

Customer pays via UPI

Display the upi_link as a QR code or "Pay Now" button. The customer must pay the exact unique_amount for automatic matching.

3

Merchant polls /api/v1/check-payment/{order_id}

Poll every 5–10 seconds until status is paid or expired. Recommended timeout: 60 minutes.

4

Credit email detected instantly (~1–3 seconds)

Our system monitors your connected Gmail account in real-time. The moment the bank credit email arrives, it is matched by amount and UTR, the order is marked verified, and your next poll returns verified.

5

Post-payment action triggered (optional)

Depending on your integration type, the engine automatically: updates the WooCommerce order to processing via REST API, fires a Webhook POST to your configured URL, or sends a WhatsApp notification.

POST /api/v1/initiate-pay

Creates a new payment session. Returns a system-assigned unique_amount and UPI deep link for the customer to pay.

Request Body (JSON)

Field Type Required Description
order_id string required Your platform's unique order identifier (e.g. WooCommerce order ID). Must be unique per merchant. Max recommended: 64 chars.
customer_email string required Customer's email address. Stored for reference and timeout notification emails.
customer_phone string required Customer's phone number. Stored for reference.
base_amount float required Order amount in INR (e.g. 500.00). The API will compute and return a unique_amount for the customer to pay — always use that value, not this one.
merchant_id string required Your merchant key (format: mch_...). Found in your merchant dashboard under Settings.

Success Response 200 OK

Field Type Description
status string Always "success" on 200.
order_id string Echoes back the order_id you sent.
unique_amount float Critical: The exact amount the customer must pay. Determined by proprietary reconciliation logic. Always display and charge this value — never the original base_amount.
upi_link string UPI Intent URL in the format upi://pay?pa=...&pn=...&am=...&cu=INR. Use this to render a QR code or "Pay Now" button on mobile.
created_at string (ISO 8601) Timestamp when the order was created (IST).
expires_at string (ISO 8601) Timestamp when the order expires (created_at + 1 hour). After expiry the order moves to failed_orders.

Request

POST /api/v1/initiate-pay
Content-Type: application/json

{
  "order_id": "WC-1042",
  "customer_email": "john@example.com",
  "customer_phone": "9876543210",
  "base_amount": 500.00,
  "merchant_id": "mch_4b7d2e9f1a3c058762de4f91"
}

Response

{
  "status": "success",
  "order_id": "WC-1042",
  "unique_amount": 500.07,
  "upi_link": "upi://pay?pa=merchant@upi&pn=Shop&am=500.07&cu=INR",
  "created_at": "2026-05-14T18:30:00",
  "expires_at": "2026-05-14T19:30:00"
}
GET /api/v1/check-status/{order_id}

Returns full order details including status, amount, and timestamps. Useful for debugging or displaying a detailed status page.

Query Parameters

Parameter Type Required Description
order_id string (path) required The order_id you provided when creating the order.
merchant_id string (query) required Your merchant key. Scopes the lookup to your account.

Response Fields

Field Type Description
order_idstringYour order identifier.
statusstringpending, verified, or expired. Note: this endpoint returns the internal status (verified, not paid).
amountfloatThe unique amount assigned to this order.
created_atstringISO 8601 timestamp when order was created (IST).
expires_atstringISO 8601 timestamp when order expires (IST).

Request

GET /api/v1/check-status/WC-1042?merchant_id=mch_4b7d2e9f1a3c058762de4f91

Response

{
  "order_id": "WC-1042",
  "status": "verified",
  "amount": 500.07,
  "created_at": "2026-05-14T18:30:00",
  "expires_at": "2026-05-14T19:30:00"
}

// Not found response:
{ "error": "Order not found" }
GET /api/v1/check-payment/{order_id} Use for polling

Lightweight polling endpoint. Returns a simple success flag and status field. This is the primary endpoint to poll while waiting for payment confirmation.

Query Parameters

Parameter Type Required Description
order_idstring (path)requiredThe order ID.
merchant_idstring (query)requiredYour merchant key.

Response — All Possible States

success status Meaning Action
truependingOrder created, awaiting paymentContinue polling
trueverifiedPayment received and verified ✅Mark order complete
trueexpiredOrder not paid within 1 hourStop polling, show failure
falsenot_foundWrong order_id or merchant_idStop polling, check IDs

Request

GET /api/v1/check-payment/WC-1042?merchant_id=mch_4b7d2e9f1a3c058762de4f91

Responses

// Still waiting:
{ "success": true, "status": "pending" }

// Payment confirmed:
{ "success": true, "status": "verified" }

// Not found:
{ "success": false, "status": "not_found" }

Order Statuses

All possible states an order can be in

pending

Order created, waiting for payment. Active for up to 1 hour. The UPI link is valid and the payment session is active.

verified

Payment received and matched to this order via Gmail parsing. Both /check-payment and /check-status return this as "verified".

expired

Order was not paid within 1 hour and was moved to failed_orders. Can be manually verified from the merchant dashboard if payment was received late.

not_found

Only returned by /check-payment. Means the order_id + merchant_id combination doesn't exist in the database.

Polling Strategy

Best practices for polling /check-payment

Recommended intervals:

  • Poll every 5 seconds for the first 2 minutes (most payments complete by then)
  • Poll every 15 seconds from 2–10 minutes
  • Poll every 30 seconds from 10–60 minutes
  • Stop polling after 60 minutes — order will have expired
// JavaScript polling example
async function pollPayment(orderId, merchantId) {
  const maxTime = 60 * 60 * 1000; // 1 hour
  const start = Date.now();
  let interval = 5000; // start at 5s

  while (Date.now() - start < maxTime) {
    const res = await fetch(
      `/api/v1/check-payment/${orderId}?merchant_id=${merchantId}`
    );
    const data = await res.json();

    if (data.status === 'paid') {
      // Payment confirmed!
      handleSuccess(data);
      return;
    }
    if (!data.success) {
      handleError('Order not found');
      return;
    }

    // Back off over time
    if (Date.now() - start > 10 * 60 * 1000) interval = 30000;
    else if (Date.now() - start > 2 * 60 * 1000) interval = 15000;

    await new Promise(r => setTimeout(r, interval));
  }

  handleTimeout(); // 1 hour passed
}

Proprietary Reconciliation

How Alvan Labs Pay matches payments to orders automatically

Alvan Labs Pay uses proprietary reconciliation technology to automatically match incoming UPI payments to the correct order — without any manual intervention. When you initiate a payment, the system assigns a unique_amount for that session. This amount must be paid exactly as specified.

⚠️ Important for merchants:
  • Always display and charge the unique_amount returned by the API — never the original base_amount.
  • The unique_amount may differ slightly from base_amount. The difference, if any, is negligible and is by design.
  • If the maximum concurrent payment sessions are reached, the API returns 503. This resolves automatically as sessions complete or expire.

Order Expiry

What happens when an order is not paid within 1 hour

  • ✅ Orders expire 1 hour after creation.
  • ✅ An automated background job runs every minute to detect expired orders and move them to failed_orders.
  • ✅ A timeout email is sent to the customer's email address when an order expires.
  • ✅ Expired orders are visible in the merchant dashboard under the "Expired" filter.
  • ✅ Merchants can manually verify an expired order from the dashboard if they received payment after expiry.
  • ⚠️ After expiry, the payment session is released and new orders can be initiated for the same amount.

WooCommerce Integration

Built-in automatic order status sync

When your merchant account has integration_type = woo configured, the system automatically calls the WooCommerce REST API to update the order to processing when payment is verified. No additional code needed.

Required settings (configure in merchant dashboard → Settings):

SettingValue
woo_api_urlhttps://yourstore.com/wp-json/wc/v3
woo_consumer_keyWooCommerce REST API Consumer Key (ck_...)
woo_consumer_secretWooCommerce REST API Consumer Secret (cs_...)
integration_typeMust be set to woo
Note: The WooCommerce order ID you pass as order_id to /initiate-pay must be the numeric WooCommerce order ID. The system uses PUT /wp-json/wc/v3/orders/{order_id} to update the status.

Shopify Integration

Native Shopify Checkout Extension with Direct Deep-Linking

Accept zero-fee UPI payments directly in your Shopify Store using our dedicated Shopify App. The integration embeds a native Checkout UI Extension block directly on your store's **Thank You** and **Order Status** pages.

📱 Native Mobile & iOS Safari Deep-Linking:

Our Shopify extension uses official sandboxed link hooks. On mobile devices (including Safari and Chrome on iOS), customers can simply tap an interactive button to instantly launch their local UPI app (GPay, PhonePe, Paytm, or BHIM) with pre-filled and locked payment amounts, boosting mobile conversion rates significantly.

✨ Key Features:
  • Zero Code Required: One-click merchant installation via your AlvanLabs Pay dashboard.
  • Clear Price Breakdown: Separates the base item amount from verification charges transparently on screen.
  • Real-time Fulfillment: As soon as the email parsing matches the credit bank notification, the Shopify order is automatically tagged and marked as Paid.

Webhooks Coming Soon

Push notifications when payment is verified

When integration_type = webhook, the system will POST a JSON payload to your configured webhook_url when an order is verified. The webhook payload format will be:

// Planned webhook payload (subject to change)
{
  "event": "payment.verified",
  "order_id": "WC-1042",
  "amount": 500.07,
  "merchant_id": "mch_...",
  "verified_at": "2026-05-14T18:32:11"
}

Contact payments@alvanlabs.com to request early access.

Custom PHP Integration

Full example for non-WooCommerce PHP applications

<?php
// ─── Step 1: Initiate payment ───────────────────────────────
$merchantId = 'mch_4b7d2e9f1a3c058762de4f91';
$orderId    = 'ORDER-' . time();  // your unique order ID

$payload = json_encode([
    'order_id'       => $orderId,
    'customer_email' => 'customer@example.com',
    'customer_phone' => '9876543210',
    'base_amount'    => 500.00,
    'merchant_id'    => $merchantId,
]);

$ch = curl_init('https://payments.alvanlabs.com/api/v1/initiate-pay');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

if (!$response || $response['status'] !== 'success') {
    die('Payment initiation failed: ' . ($response['detail'] ?? 'unknown error'));
}

$uniqueAmount = $response['unique_amount'];   // e.g. 500.07
$upiLink      = $response['upi_link'];        // display as QR or button
$expiresAt    = $response['expires_at'];      // show countdown timer

// Store $orderId + $uniqueAmount in your DB for reference

// ─── Step 2: Poll for payment status (server-side or AJAX) ─
function checkPaymentStatus($orderId, $merchantId): string {
    $url = "https://payments.alvanlabs.com/api/v1/check-payment/{$orderId}?merchant_id={$merchantId}";
    $ch = curl_init($url);
    curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5]);
    $data = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $data['status'] ?? 'error';
}

// In your AJAX endpoint:
$status = checkPaymentStatus($_GET['order_id'], $merchantId);
echo json_encode(['status' => $status]);  // "pending" | "paid" | "not_found"
?>

cURL Examples

Quick reference for testing all three endpoints

Initiate Payment

curl -X POST https://payments.alvanlabs.com/api/v1/initiate-pay \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "TEST-001",
    "customer_email": "test@example.com",
    "customer_phone": "9876543210",
    "base_amount": 500.00,
    "merchant_id": "mch_4b7d2e9f1a3c058762de4f91"
  }'

Check Payment (for polling)

curl "https://payments.alvanlabs.com/api/v1/check-payment/TEST-001?merchant_id=mch_4b7d2e9f1a3c058762de4f91"

Check Status (full details)

curl "https://payments.alvanlabs.com/api/v1/check-status/TEST-001?merchant_id=mch_4b7d2e9f1a3c058762de4f91"

Frequently Asked Questions

What happens if the customer pays the wrong amount?
The order will remain pending until it expires. The payment will still reach the merchant's UPI account, but it won't be automatically matched to the order. The merchant can manually verify the order from the dashboard if needed.
How long does verification take after payment?
Typically 1–3 seconds. Our system uses real-time email monitoring — when the bank credit email arrives in your Gmail, verification is triggered instantly. The exact delay depends on when the bank sends the credit notification after the UPI payment is processed.
Can I reuse the same order_id?
No. Each order_id must be unique per merchant. If you try to create an order with a duplicate order_id, you will get a database constraint error. Use a timestamp or UUID suffix if needed.
What is the maximum number of concurrent pending orders?
The system supports a high number of simultaneous payment sessions per merchant. If capacity is reached, the API returns 503 — this resolves automatically as sessions complete or expire. For very high-volume use cases, contact support.
Does the API work with all UPI apps?
Yes. The UPI link is a standard intent URL and works with GPay, PhonePe, Paytm, BHIM, and all UPI-compliant apps. The upi://pay scheme is supported by all Android UPI apps. On iOS, QR code scanning is the recommended approach.
Is HTTPS required?
Yes. All API endpoints are served exclusively over HTTPS at https://payments.alvanlabs.com. HTTP requests will be redirected to HTTPS by nginx.
How do I contact support?
Email us at payments@alvanlabs.com. You can also use the "Facing issue? Contact us" button in your merchant dashboard.

Alvan Labs Pay · API v1 · payments@alvanlabs.com

© 2026 Alvan Labs. All rights reserved.