Developers · REST API
One API for billing, payments and invoices.
Customers, plans, subscriptions, usage events, wallets, invoices, checkout and benefits under /api/v1, documented in OpenAPI 3.1. This page is the map; the API reference has every endpoint.
- tola_test_•••• 7f2kLast used 2 min agoTest
- tola_live_•••• m4abRotating · 24 h graceLive
Secrets are stored as SHA-256 hashes and shown once, at creation.
In short
How do I integrate TolaPay into my application?
Create a workspace and copy a tola_test_ key. From your backend, create a customer, then either subscribe them to a plan, send usage events, or open a hosted checkout and redirect them to it. Register a webhook endpoint, verify the Tola-Signature header on each delivery, and grant access when invoice.paid arrives. Everything runs against sandbox Khalti, eSewa and Stripe until you switch to a tola_live_ key.
Quickstart
5 steps to a paid invoice.
- 01
Create a customer
Name and email are required. Store your own id in external_id so later calls can use it instead of TolaPay's.
curl https://api.tola.com.np/api/v1/customers \ -H "Authorization: Bearer $TOLA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: customer-phewa-001" \ -d '{ "name": "Phewa Studio Pvt. Ltd.", "email": "accounts@phewa.example", "external_id": "phewa-001" }'const tola = (path, body) => fetch(`https://api.tola.com.np/api/v1${path}`, { method: "POST", headers: { Authorization: `Bearer ${process.env.TOLA_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify(body), }).then((r) => r.json()); const customer = await tola("/customers", { name: "Phewa Studio Pvt. Ltd.", email: "accounts@phewa.example", external_id: "phewa-001", }); - 02
Subscribe them to a plan
Plans are set up once in the dashboard or the API. A subscription links a customer to a plan; collection_method decides how renewals are paid.
curl https://api.tola.com.np/api/v1/subscriptions \ -H "Authorization: Bearer $TOLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cus_7k2mq9vd4r8xw3ba6tyn", "plan_id": "plan_g4wv8t2kq6m1xr9zb3dn", "seats": 3, "collection_method": "invoice_link" }'const subscription = await tola("/subscriptions", { customer_id: customer.id, plan_id: "plan_g4wv8t2kq6m1xr9zb3dn", seats: 3, collection_method: "invoice_link", // or "auto_charge", "wallet" }); - 03
Record usage or open a checkout
Send one event per billable action, with a transaction_id that makes retries safe. Events accept your external_id as the customer. Or create a hosted checkout and redirect the customer to its url.
curl https://api.tola.com.np/api/v1/events \ -H "Authorization: Bearer $TOLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "phewa-001", "event_name": "ai_minutes", "transaction_id": "call_8f41c2", "properties": { "minutes": 4.5 } }' # → 202 { "status": "processed", ... } # Same transaction_id again → 202 { "status": "duplicate" }const checkout = await tola("/checkouts", { customer_id: customer.id, plan_id: "plan_g4wv8t2kq6m1xr9zb3dn", allowed_providers: ["khalti", "esewa", "stripe"], success_url: "https://app.chautari.example/welcome?checkout={CHECKOUT_ID}", }); // Send the customer to checkout.url - 04
Receive a verified webhook
TolaPay signs every delivery. Verify Tola-Signature against the raw body, reject stale timestamps, then act on the event idempotently.
Node.jsimport crypto from "node:crypto"; // rawBody: the request body exactly as received (a string, not parsed JSON) export function verifyTolaSignature(header, rawBody, secret) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const expected = crypto .createHmac("sha256", secret) // your endpoint secret, whsec_… .update(`${parts.t}.${rawBody}`) .digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; const valid = expected.length === parts.v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)); return fresh && valid; } // if (event.type === "invoice.paid") grant access for event.data.customer_id - 05
Track payment status
Invoices and payments are queryable at any time. The dashboard's API log shows every request with its response.
curlcurl "https://api.tola.com.np/api/v1/invoices?customer_id=cus_7k2mq9vd4r8xw3ba6tyn&status=finalized" \ -H "Authorization: Bearer $TOLA_API_KEY" # → { "data": [ { "id": "inv_…", "status": "finalized", "total_cents": 703764, ... } ], # "page": 1, "limit": 50, "total": 1 }
Authentication
Test and live, kept apart.
Authorization: Bearer tola_test_…ortola_live_…- Secrets are shown once, stored as SHA-256 hashes, and rotate with a 24-hour grace period.
- Keys carry a role (viewer, support, finance, developer, admin, owner), capped at the role of the person who created them.
- Every request appears in the dashboard's API log with its response.
Conventions/api/v1
- Base URL
- https://api.tola.com.np/api/v1
- Authentication
- Authorization: Bearer tola_test_… or tola_live_…. Secrets are shown once; afterwards only the prefix and last four characters.
- Money
- Integers in minor units in *_cents fields, with an ISO currency (default NPR). 1500000 = NPR 15,000.00.
- IDs
- Prefixed strings: cus_, plan_, sub_, evt_, inv_, pay_ and so on.
- Idempotency
- Send Idempotency-Key on any POST; the response is replayed for 24 hours. Usage events are also de-duplicated by transaction_id.
- Errors
- An HTTP status and { "error": { "type", "message", "param" } }, with types such as validation_error, unauthorized, not_found, conflict and rate_limited.
- Rate limits
- 600 requests per minute per API key, with X-RateLimit-Limit and X-RateLimit-Remaining headers and Retry-After on 429.
- Lists
- page, limit (up to 200), q and status; responses carry data, page, limit and total.
Webhooks
Webhooks you can trust.
TolaPay POSTs each event to your endpoint with a Tola-Signature header: t=<unix seconds>,v1=<hex HMAC>.
- 01Split the header on commas and read t and v1.
- 02Compute HMAC-SHA256 over "<t>.<raw body>" with your endpoint secret (whsec_…).
- 03Compare the hex digest to v1 in constant time.
- 04Reject the request if t is more than five minutes from now.
Respond with any 2xx within 10 seconds. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours.
POST https://api.chautari.example/tola
- Tola-Event
- invoice.paid
- Tola-Signature
- t=1792311420,v1=5f0c9e…b81a
- User-Agent
- Tola-Webhooks/1.0
{
"id": "we_…",
"type": "invoice.paid",
"created_at": "2026-10-18T08:17:00Z",
"data": {
"number": "INV-2083-0142",
"customer_id": "cus_7k2mq9vd4r8xw3ba6tyn",
"total_cents": 703764,
"currency": "NPR",
"status": "paid"
}
}HMAC-SHA256 over the timestamp and raw body. Retried on failure.
Events you'll use most
- customer.created
- subscription.created
- subscription.updated
- subscription.cancelled
- invoice.finalized
- invoice.paid
- invoice.failed
- payment.succeeded
- payment.refunded
- usage.received
- wallet.low_balance
- spend.hard_limit_reached
- benefit_grant.created
- order.completed
Errors
Errors and retries.
Errors return an HTTP status and a body of {"error": {"type", "message", "param"}}. Send an Idempotency-Key on POSTs and retry safely: the same response is replayed for 24 hours.
| type | Status | Meaning |
|---|---|---|
| validation_error | 400 | A field is missing or malformed; param names it. |
| unauthorized | 401 | Missing or invalid API key. |
| payment_required | 402 | The action needs a payment first. |
| forbidden | 403 | The key's role is too low for this call. |
| not_found | 404 | A referenced customer, plan or product doesn't exist. |
| conflict | 409 | The request conflicts with the current state. |
| idempotency_key_reused | 422 | Same Idempotency-Key, different body. |
| rate_limited | 429 | Over 600 requests a minute; wait for Retry-After. |
Less code
Less code, when that's what you need.
<script src="https://api.tola.com.np/tola.js" defer></script>
<a href="https://app.tola.com.np/l/tcl_your_link_secret" data-tola-checkout>
Subscribe
</a>
<script>
// Fired by tola.js: checkout.opened, checkout.closed, checkout.success
</script>Questions
Developer questions.
The OpenAPI document is public at /api/v1/openapi.yaml, so you can generate a client in any language.
Can I integrate TolaPay into my SaaS application?
Yes. TolaPay has a REST API under /api/v1, documented in OpenAPI 3.1, covering customers, plans, subscriptions, usage events, wallets, invoices, checkout, benefits and analytics. Most integrations start with three calls: create a customer, record usage or open a checkout, and handle the invoice.paid webhook.
Does TolaPay provide a sandbox?
Yes. Every workspace has a test mode with separate tola_test_ API keys and sandbox Khalti, eSewa and Stripe, so you can run the full flow, from checkout to webhook, before switching to tola_live_ keys.
How are webhooks verified?
Each webhook carries a Tola-Signature header with a timestamp and an HMAC-SHA256 of the timestamp and raw body, computed with your endpoint secret. Compare it in constant time and reject anything older than five minutes. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours.
Start in test mode today.
Sandbox Khalti, eSewa and Stripe are ready the moment you create a workspace. Go live when your integration is.
Free while TolaPay is in public beta. You pay only your payment providers' own fees.