Skip to content

Documentation

Get started with upiagent

Accept UPI payments directly into your bank account. No payment gateway, no fees, no merchant onboarding. Install the package, add your API key, done.

Install

terminal
bash
npm install upiagent

Zero native dependencies. Works in Node.js 18+, Next.js, Express, or any server-side JS.

Quick start

Sign up at beta-dashboard.upiagent.live, connect your Gmail and UPI ID, grab your API key. Then it's 5 lines:

checkout.ts
typescript
import { UpiAgent } from "upiagent/client";

const upi = new UpiAgent({ apiKey: process.env.UPIAGENT_API_KEY });

// 1. Create payment — returns a QR code
const payment = await upi.createPayment({
  amount: 499,
  note: "Order #123",
  addPaisa: true,  // ₹499 → ₹499.37 (unique amount for matching)
});

// 2. Show QR to customer
// payment.qrDataUrl  → base64 PNG for <img src={...} />
// payment.intentUrl  → upi://pay?... for mobile deep link

// 3. Verify — checks your Gmail for bank alert automatically
const result = await upi.verify(payment.id);

if (result.verified) {
  console.log("Paid!", result.payment.senderName, result.payment.upiReferenceId);
}
That's it. No Gmail credentials, no Google Cloud project, no LLM API keys. You set all of that up once in the dashboard — the SDK just uses your API key.

Create & wait (one call)

For the simplest integration, use createAndWaitForPayment() — it creates the payment, polls for verification, and returns when done.

create-and-wait.ts
typescript
import { UpiAgent } from "upiagent/client";

const upi = new UpiAgent({ apiKey: process.env.UPIAGENT_API_KEY });

const payment = await upi.createAndWaitForPayment(
  { amount: 499, note: "Order #123", addPaisa: true },
  {
    onPaymentCreated: (p) => {
      // Show QR to customer
      console.log("Scan to pay:", p.qrDataUrl);
    },
    onStatusUpdate: (p) => {
      console.log("Status:", p.status);
    },
    pollInterval: 5000,   // check every 5s
    timeout: 180_000,     // give up after 3 min
  }
);

if (payment.status === "verified") {
  console.log("Payment confirmed!", payment.senderName);
}

Next.js integration

API route

app/api/pay/route.ts
typescript
// app/api/pay/route.ts
import { UpiAgent } from "upiagent/client";

const upi = new UpiAgent({ apiKey: process.env.UPIAGENT_API_KEY! });

export async function POST(req: Request) {
  const { amount, orderId } = await req.json();

  const payment = await upi.createPayment({
    amount,
    note: `Order ${orderId}`,
    addPaisa: true,
  });

  return Response.json({
    id: payment.id,
    qrDataUrl: payment.qrDataUrl,
    intentUrl: payment.intentUrl,
    amount: payment.amount,
  });
}

// app/api/pay/[id]/route.ts
export async function GET(
  _req: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const status = await upi.getStatus(id);
  return Response.json(status);
}

React component

components/checkout.tsx
tsx
"use client";
import { useState, useEffect } from "react";

export function Checkout({ amount }: { amount: number }) {
  const [payment, setPayment] = useState<any>(null);
  const [status, setStatus] = useState("idle");

  async function createPayment() {
    const res = await fetch("/api/pay", {
      method: "POST",
      body: JSON.stringify({ amount, orderId: "ORD_123" }),
    });
    const data = await res.json();
    setPayment(data);
    setStatus("pending");
  }

  // Poll for verification
  useEffect(() => {
    if (status !== "pending" || !payment) return;
    const interval = setInterval(async () => {
      const res = await fetch(`/api/pay/${payment.id}`);
      const data = await res.json();
      if (data.status === "verified") {
        setStatus("verified");
        clearInterval(interval);
      }
      if (data.status === "expired") {
        setStatus("expired");
        clearInterval(interval);
      }
    }, 5000);
    return () => clearInterval(interval);
  }, [status, payment]);

  if (status === "idle") {
    return <button onClick={createPayment}>Pay \u20b9{amount}</button>;
  }

  if (status === "pending") {
    return (
      <div>
        <img src={payment.qrDataUrl} alt="Scan to pay" width={200} />
        <a href={payment.intentUrl}>Open UPI App</a>
        <p>Waiting for payment...</p>
      </div>
    );
  }

  if (status === "verified") return <p>Payment confirmed!</p>;
  return <p>Payment expired. Try again.</p>;
}

Express integration

server.ts
typescript
import express from "express";
import { UpiAgent } from "upiagent/client";

const app = express();
app.use(express.json());

const upi = new UpiAgent({ apiKey: process.env.UPIAGENT_API_KEY! });

app.post("/pay", async (req, res) => {
  const payment = await upi.createPayment({
    amount: req.body.amount,
    note: req.body.note,
    addPaisa: true,
  });
  res.json(payment);
});

app.get("/pay/:id", async (req, res) => {
  const status = await upi.getStatus(req.params.id);
  res.json(status);
});

app.post("/pay/:id/verify", async (req, res) => {
  const result = await upi.verify(req.params.id);
  res.json(result);
});

app.listen(3000);

SDK reference

UpiAgent(config)

Create a client instance. All you need is your API key.

ParamTypeDescription
apiKeystringYour API key from the dashboard
baseUrl?stringOverride API URL (default: https://beta.upiagent.live)

upi.createPayment(params)

Create a payment and get a QR code. Customers scan with any UPI app — GPay, PhonePe, Paytm, CRED.

ParamTypeDescription
amountnumberAmount in INR
note?stringNote shown in UPI app
addPaisa?booleanAdd random paisa for unique matching (recommended)

Returns a Payment with qrDataUrl (base64 PNG), intentUrl (UPI deep link), id, amount, and status.

upi.verify(paymentId)

Trigger verification — scans Gmail for a matching bank alert. Call after the customer has paid.

verify.ts
typescript
const result = await upi.verify(payment.id);

// result.verified    → true if payment matched
// result.payment     → { amount, upiReferenceId, senderName, bankName, confidence }
// result.status      → "verified" | "pending" | "expired"
// result.message     → failure reason if not verified

upi.getStatus(paymentId)

Read-only status check — no verification triggered.

upi.createAndWaitForPayment(params, options)

Create + poll in one call. Returns when verified, expired, or timeout.

OptionTypeDescription
onPaymentCreated?fnCalled with payment after creation (show QR here)
onStatusUpdate?fnCalled on each poll
pollInterval?numberMs between polls (default: 5000)
timeout?numberMs before giving up (default: 180000)

Webhooks

Set your webhook URL in the dashboard settings. We POST to it when payments are verified. HMAC-signed for security.

Webhook payload

webhook-payload.json
json
{
  "event": "payment.verified",
  "data": {
    "paymentId": "uuid",
    "amount": 499.37,
    "currency": "INR",
    "status": "verified",
    "upiReferenceId": "003538060093",
    "senderName": "JOHN DOE",
    "confidence": 0.95,
    "verifiedAt": "2026-05-03T10:30:00Z"
  }
}

Verify signature

app/api/webhooks/route.ts
typescript
import { verifyWebhookSignature } from "upiagent";

export async function POST(req: Request) {
  const body = await req.text();
  const signature = req.headers.get("x-upiagent-signature")!;

  if (!verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET!)) {
    return new Response("Invalid signature", { status: 401 });
  }

  const payload = JSON.parse(body);
  if (payload.event === "payment.verified") {
    // Credit the customer, fulfill the order
  }

  return new Response("ok");
}

Headers

HeaderValue
X-UpiAgent-Signaturesha256=HMAC hex digest
X-UpiAgent-Eventpayment.verified
X-UpiAgent-Delivery-IdUnique ID for idempotency

Security layers

Every payment goes through 5 validation layers. If any layer fails, verification stops immediately.

1

Email source

Is the email from a known bank? Checked against a registry of bank sender patterns (ICICI, HDFC, Axis, SBI, etc.).

2

Amount match

Does the parsed amount match exactly? Zero tolerance by default.

3

Time window

Was the payment within the lookback window? Blocks stale/replayed emails.

4

LLM confidence

AI confidence in parsing the email. Low confidence = verification fails.

5

Deduplication

Has this UPI reference been used before? Prevents double-crediting.

Error handling

errors.ts
typescript
import { UpiAgent, UpiAgentApiError } from "upiagent/client";

const upi = new UpiAgent({ apiKey: process.env.UPIAGENT_API_KEY! });

try {
  const payment = await upi.createPayment({ amount: 499, addPaisa: true });
} catch (err) {
  if (err instanceof UpiAgentApiError) {
    console.error(err.status, err.message);
    // 401 → invalid API key
    // 429 → rate limit or daily token cap
    // 404 → payment not found
  }
}
Verification failures(amount mismatch, no matching email, etc.) don't throw errors. They return { verified: false, message: "..." }. Only infrastructure failures (auth, rate limits) throw.

How it works

When you sign up, you connect your Gmail (the one that receives bank alerts like "You have received Rs.499 from...") and your UPI ID. upiagent handles the rest:

1.You call createPayment() — we generate a QR code with your UPI ID
2.Customer scans and pays — money goes directly to your bank account
3.Your bank sends a confirmation email to your Gmail
4.You call verify() — our AI reads the bank email and confirms the payment
5.We return the sender name, UPI reference ID, and confidence score
No payment gateway. Money flows directly from customer to your bank via UPI. upiagent only reads emails to confirm it happened. You keep 100% of every payment.

Ready to start?

npm install upiagent

Then grab your API key from the dashboard