Developer docs
Signed REST API for creating and managing UPI orders. Free to use — every sale, on every surface, is free; see Pricing. Money never passes through IndieVouch; order creation just points a UPI intent at your own VPA.
Authentication
Send your API key as Authorization: Bearer <key> (or the X-Api-Key header). Every state-changing call also carries a signature: HMAC-SHA256 of amount:order_id:timestamp, keyed with your API secret, plus a timestamp that must be within 500 seconds of the server's clock — replay protection and idempotency a bare bearer token doesn't give you. Generate your key and secret from the Developer tab in your dashboard once you've signed up.
import hashlib, hmac, time
secret = "sk_your_api_secret"
amount, order_id = 499, "IP-1001"
timestamp = str(int(time.time()))
signature = hmac.new(
secret.encode(),
f"{amount:g}:{order_id}:{timestamp}".encode(),
hashlib.sha256,
).hexdigest()
Create an order
Returns the UPI intent string and a hosted pay page you can redirect to. Use type: "api" for the full payload, or "api_link" when you only need the hosted link.
curl -X POST https://indievouch.in/api/v1/merchant/order/create \
-H "Authorization: Bearer pk_your_key" -H "Content-Type: application/json" \
-d '{"type":"api","amount":499,"vpa":"you@okhdfcbank",
"customer_name":"Aarav Sharma","customer_email":"aarav@example.com",
"description":"Order #IP-1001","order_id":"IP-1001",
"timestamp":"1760000000","signature":"<hmac>"}'
The response carries base_amount (the price), adjustment (paise shaved in auto-verification mode) and amount (what the buyer actually pays) — sign and display base_amount, charge amount. type: "api" also returns upi_uri to render your own intent/QR; type: "api_link" gives you a pay_url to redirect to.
Endpoints
| POST /api/v1/merchant/order/create | Create an order |
| GET /api/v1/merchant/orders | List, filter by status or source |
| GET /api/v1/merchant/order/{id} | Fetch one, by trx_id or your order_id |
| POST /api/v1/merchant/order/{id}/utr | Record the customer's reference |
| POST /api/v1/merchant/order/{id}/settle | Force completed or failed |
| GET /api/v1/merchant/nodes | Your payment nodes |
| GET /api/v1/merchant/node-entries | Observed credits, linked or not |
Rejected on purpose: bad signature (401), stale timestamp (401), duplicate order_id (409), a vpa that isn't one of your active nodes (422), localhost/internal redirect_url (422). Machine-readable schema at /openapi.json; interactive reference at /api/docs.
Rate limits
Every /api/v1 response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers (plus the IETF RateLimit / RateLimit-Policy header fields), keyed per API key: 120 requests per 60-second window. Exceeding it returns 429 with a Retry-After header — self-throttle from the headers rather than guessing.
Webhooks
Fire on transaction.completed, transaction.flagged, transaction.failed and transaction.refunded, with X-IndieVouch-Signature: v1=<hmac of "{timestamp}.{raw body}"> and X-IndieVouch-Timestamp. Delivery is retried at 0s/2s/5s; every attempt is logged with its HTTP status under your dashboard's Developer tab.
Versioning & deprecation policy
The API is versioned in the URL (/api/v1). We don't ship breaking changes to a version once released. If a /api/v2 is ever introduced, /api/v1 keeps working for at least 12 months afterward, and any endpoint scheduled for removal is signaled in advance with Deprecation and Sunset response headers (RFC 8594) on the affected calls, ahead of the documented removal date.
Setting up automatic confirmation
Automatic reconciliation (MacroDroid, Tasker, Automate, or the iPhone Web Verify Shortcut) is configured per-account, since it needs your own device token or verify key — see the Developer tab in your dashboard once you've signed up.