# Wallet and Billing API

> Run the Edesy prepaid wallet from your own admin portal - read the balance, recharge with Razorpay Checkout or a hosted payment link, confirm the recharge landed, and get wallet.low_balance and wallet.recharged webhooks.

Source: https://edesy.in/docs/voice-api/account/wallet-and-billing-api

The Wallet and Billing API lets your own admin portal do what the wallet page does: show the balance, take a recharge, confirm it was credited, and get told when the balance runs low. You recharge either with Razorpay Checkout on your page or with a hosted payment link you redirect to or send. Two webhooks, `wallet.low_balance` and `wallet.recharged`, tell your server what happened.

Base URL `https://voice-api.edesy.in/v1`. Every call is made from your server with `Authorization: Bearer <API key>`; the key can spend your wallet, so never put it in browser code.

## Facts

| Item | Detail |
|---|---|
| Currency | INR. The wallet holds money before GST: a Rs 500 recharge charges Rs 590 and adds Rs 500 |
| Minimum recharge | Rs 10 |
| Recharge methods | Razorpay Checkout on your page, or a hosted Razorpay payment link |
| Rate limit | Create Recharge Order and Create Payment Link share 10 a minute per account; over it returns `429` with `Retry-After` |
| Payment link expiry | 7 days by default; 16 minutes to 180 days with `expire_in_minutes` |
| Confirmation | `wallet.recharged` webhook, or poll Get Payment until `completed` |
| Low-balance threshold | Rs 100 unless agreed otherwise |
| Invoices | A GST tax invoice for every completed recharge, as HTML or PDF |

## Endpoints

| Method | Path | Description |
|---|---|---|
| `GET` | `/billing` | Balance, this month's usage, your rates and the account state |
| `POST` | `/billing/create-order` | Create a Razorpay order for Checkout |
| `POST` | `/billing/verify-payment` | Optional: credit a Checkout payment straight away |
| `POST` | `/billing/payment-links` | Create a hosted payment link |
| `GET` | `/billing/payments/{id}` | One recharge and its status |
| `GET` | `/billing/transactions` | The wallet ledger (`.csv` for an export) |
| `GET` | `/billing/invoices` | Completed recharges with invoice numbers |
| `GET` | `/billing/payments/{id}/invoice?format=pdf` | Download an invoice |
| `POST` | `/webhooks/subscriptions` | Subscribe to wallet events |

## Show the balance

```bash
curl https://voice-api.edesy.in/v1/billing -H "Authorization: Bearer $EDESY_KEY"
```

`balance` is what you can spend. `account_state.status` tells you when to prompt a recharge: `ok`, `low_balance` (below the threshold), `past_due` (negative balance or an unpaid number rental) or `suspended`. `outbound_blocked` says whether outbound calls are being refused.

## Recharge with Checkout

The payer stays on your page while Razorpay's payment window opens on top of it.

```bash
curl -X POST https://voice-api.edesy.in/v1/billing/create-order \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 500 }'
```

```json
{ "data": { "order_id": "order_...", "payment_id": "...", "key_id": "rzp_live_...",
  "subtotal": 500, "tax_rate": 0.18, "tax_amount": 90, "total": 590, "currency": "INR" } }
```

1. Your server creates the order. `amount` is what to credit, before GST. Add `gstin` to print your GSTIN on the invoice.
2. Your page opens Razorpay Checkout with `key_id`, `order_id` and `total x 100` (paise). Only `order_id` and `key_id` reach the browser.
3. When Razorpay captures the payment, the wallet is credited automatically, even if the payer closes the window. Calling `POST /billing/verify-payment` with `razorpay_order_id`, `razorpay_payment_id` and `razorpay_signature` from the success handler is optional and only makes the credit instant. Calling it twice is safe.

Create an order when the payer clicks to pay, not ahead of time: orders and links share the 10-a-minute limit.

## Recharge with a payment link

No Razorpay code on your side. You get a hosted page URL to redirect to, or to send by email or WhatsApp.

```bash
curl -X POST https://voice-api.edesy.in/v1/billing/payment-links \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 500, "callback_url": "https://admin.example.com/wallet/paid" }'
```

| Field | Required | Description |
|---|---|---|
| `amount` | yes | INR to credit, before GST. Minimum 10 |
| `gstin` | no | GSTIN for the tax invoice |
| `callback_url` | no | `https://` page the payer returns to after paying |
| `expire_in_minutes` | no | Default 10080 (7 days); 16 minutes to 180 days |
| `description` | no | Shown on the payment page |

The response has `short_url`, `payment_link_id`, `payment_id`, the GST breakdown and `expires_at`. Razorpay does not SMS or email the link: delivering it is up to you.

## Know when it landed

A redirect to `callback_url`, or Checkout's success handler, only means the payer finished on Razorpay's side. Credit your own records on one of these:

- **Webhook:** `wallet.recharged`, once per credited recharge, carrying the `payment_id`.
- **Poll:** `GET /billing/payments/{id}` with the `payment_id`, the `order_...` id or the `plink_...` id, until `status` is `completed`.

| `status` | Meaning |
|---|---|
| `pending` | Not paid yet |
| `completed` | Credited; `invoice_number` is set |
| `failed` | For example, an expired link |
| `refunded` | Refunded |

## Wallet webhooks

Edesy already emails the account owner when the balance runs low. Subscribe a webhook to get the same signal on your server:

```bash
curl -X POST https://voice-api.edesy.in/v1/webhooks/subscriptions \
  -H "Authorization: Bearer $EDESY_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://admin.example.com/edesy/webhooks",
        "event_types": ["wallet.low_balance", "wallet.recharged"],
        "secret": "a-long-random-string" }'
```

| Event | When |
|---|---|
| `wallet.low_balance` | A charge takes the balance below the threshold, and again once a day while it stays below and you hold an active number |
| `wallet.recharged` | A recharge is credited |

```json
{ "event": "wallet.recharged", "timestamp": "...",
  "data": { "account_sid": "AC_...", "balance": 594.5, "currency": "INR", "amount": 500, "total": 590,
            "payment_id": "...", "invoice_number": "...", "occurred_at": "..." } }
```

- List the wallet events by name in `event_types`. A subscription with an empty list ("all events") receives call events only, never wallet events. Leave `format` out (or `legacy`).
- With a `secret`, each delivery carries `X-Edesy-Signature: sha256=<hex HMAC-SHA256 of the raw body>`; `X-Edesy-Event` carries the event name. See [Verify Signatures](https://edesy.in/docs/voice-api/webhooks/verify-signatures).
- Answer 2xx within 5 seconds. Failed deliveries are retried with backoff, up to 6 attempts, and can be replayed. See [Retries and Replay](https://edesy.in/docs/voice-api/webhooks/retries-and-replay).
- Use `payment_id` to ignore a repeat delivery.

## History and invoices

`GET /billing/transactions` is the ledger, newest first: recharges, call charges, number rentals and refunds, with the balance after each. Filter with `type` (`credit` or `debit`), `date_from`, `date_to` (IST days) and `ref_type`; `per_page` up to 200. Call rows carry `call_sid` and, where possible, an itemised `call_breakdown`. `GET /billing/transactions.csv` returns the same as CSV.

`GET /billing/invoices` lists completed recharges with their invoice numbers. Download one with `GET /billing/payments/{id}/invoice?format=pdf` (HTML without `format`).

See also [Wallet and GST Invoices](https://edesy.in/docs/voice-api/account/wallet-and-gst-invoices) for how charges are deducted.

## FAQ

### Can my customers recharge without seeing my API key?
Yes. Your server calls Edesy. The browser only ever sees a recharge's `order_id` and `key_id`, or a payment link's `short_url`.

### Whose name appears on the Razorpay window?
Edesy's, because Edesy issues the GST invoice for the recharge.

### Do I have to call Verify Payment?
No. Razorpay notifies Edesy directly and the wallet is credited without it, a few seconds later.

### Why am I getting 429?
More than 10 orders or links in a minute on one account. Wait for the `Retry-After` seconds, and create orders only when someone clicks to pay.

### Can I put my GSTIN on the invoice?
Yes. Pass `gstin` when you create the order or link, or set it in your billing profile.
