Merchant Documentation
Overview
MyPayIndia Merchants API lets you accept payments on your website or app. You create a checkout session on your server, redirect your customer to the MyPayIndia checkout page, and we redirect them back to your site once the payment is complete. You then verify the payment server-side.
To get started, you need a merchant key. To get one, you can create it yourself at https://mypayindia.com/merchant/new for a small, one-time fee.
Getting Started
You'll have two keys:
Publishable key (pk_live_...)
identifies your merchant account. It's safe to include anywhere, you can even post it to the public on your twitter account if you want. I wouldn't do that though.
Secret key (sk_live_...)
is used to authenticate yourself in API requests. This one's private. Don't give it to anyone, don't expose it anywhere publicly.
Your secret key is shown once when you generate your API keys. If you lose it or have it leaked (damn bruh), you can regenerate it from the Merchant Panel but any existing integrations using the old key will break.
Checkout Flow
- Your server calls the Create Session API with the amount, order ID, and a return URL. You authenticate with your secret key.
-
You redirect your customer to the
checkout_urlreturned by the API. - The customer logs into MyPayIndia (if not already), reviews the payment details, and clicks Pay.
-
MyPayIndia redirects the customer back to your
return_urlwith the?session_id=cs_xxx&status=completeparameters appended. -
Your server calls the Status API with the session ID to verify the payment actually completed. Do not trust the URL parameters alone.
An alternative to this is to use webhooks to get notified of transaction status changes without having to call the Status API.
Create a Checkout Session
Request Headers
| Header | Value |
|---|---|
| Authorization | Bearer sk_live_your_secret_key |
| Content-Type | application/json |
Request Body
| Parameter | Type | Description |
|---|---|---|
| amount | integer required | Amount. How much you want the customer to pay. This value expects paisa (10000 = 100.00 INR). Must be greater than 0 and lesser than 100000000 (1m INR) |
| return_url | string required | URL to redirect the customer to after payment. Must be one of your allowed domains. |
| order_id | string optional | Your order reference. A max of 64 characters are allowed. |
Example Request
curl -X POST https://mypayindia.com/api/v2/pay/create \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"amount": 25000,
"order_id": "yuri-1930",
"return_url": "https://mycoolandawesomewebsite.com/i-get-paid/i-got-paid"
}'
Example Response
{
"success": true,
"data": {
"session_id": "cs_a1b2c3d4e5f6...",
"checkout_url": "https://mypayindia.com/pay?session=cs_a1b2c3d4e5f6...",
"expires_at": "2026-04-18T15:30:00+00:00"
}
}
Errors
| Code | HTTP | Description |
|---|---|---|
| 4001 | 403 | return_url domain not in allowed domains |
| 4002 | 403 | Amount exceeds key's limit (unreviewed/unverified) |
| 9004 | 400 | Invalid or missing input |
checkout_url. Checkout sessions expire after 30 minutes.Verify Payment
After the customer is redirected back to your return_url, verify the payment
server-side. Do NOT trust the URL parameters alone.
You can verify the payment went through by calling the Status API documented below. If you don't wanna do that, you can use webhooks instead. Or you can combine both.
Request Headers
| Header | Value |
|---|---|
| Authorization | Bearer sk_live_your_secret_key |
Example Response (completed)
{
"success": true,
"data": {
"session_id": "cs_a1b2c3d4e5f6...",
"status": "complete",
"amount": 25000,
"order_id": "yuri-1930",
"transaction_id": "TXN-1234567890-12ab",
"transaction_status": "confirmed",
"sender_username": "johnpayment",
"sender_id": 69,
"created_at": "2026-04-18T15:00:00+00:00",
"expires_at": "2026-04-18T15:30:00+00:00",
"completed_at": "2026-04-18T15:01:37+00:00"
}
}
Session Statuses
| Status | Meaning |
|---|---|
| open | Waiting for the customer to pay. Checkout session is still valid. |
| complete | Customer went through checkout and a transaction was CREATED (emphasized because it may be pending or any other state, you may not have gotten your funds yet). Checkout session is closed by this point. |
| expired | Checkout session expired before the customer paid in the 30 minute window. |
Transaction Statuses
When session status is complete, the transaction_status field tells you what happened with the actual money:
| Status | Meaning |
|---|---|
| confirmed | Payment completed successfully. Funds are now in your account. |
| pending | Payment is under manual review (large transactions). Funds are not in your account yet. |
| rejected | Payment was rejected during review. Funds were returned to the sender. |
| reversed | Payment was reversed by MyPayIndia staff after being confirmed. Funds have been returned to the sender. |
Errors
| Code | HTTP | Description |
|---|---|---|
| 9005 | 404 | Session not found |
| 9004 | 400 | Invalid or missing input |
Webhooks
Webhooks notify your server when a transaction status changes. This is useful for cases where the customer closes their browser before being redirected back to your site, or when a transaction status changes after checkout (for example a pending transaction gets confirmed or reversed).
You can set up webhook endpoints in the Merchant Panel in your merchant key's settings page. You can subscribe to specific events and configure multiple endpoints.
How it works
When an event occurs, MyPayIndia sends a POST request to your endpoint URL
with a JSON payload. Your endpoint should return a 2xx status code to acknowledge
that it's received it. If your endpoint is down or returns an error, we retry with exponential backoff
(1 min, 2 min, 4 min, up to ~4 hours) before giving up.
Webhook Headers
| Header | Description |
|---|---|
| X-MPI-Event | The event type, e.g. transaction.confirmed |
| X-MPI-Signature | HMAC-SHA256 signature of the request body, signed with your endpoint's signing secret |
| X-MPI-Delivery | Unique delivery ID. Use this to deduplicate if you receive the same event twice. |
Example Payload
{
"delivery_id": "whd_a1b2c3d4e5f6...",
"event": "transaction.confirmed",
"created_at": "2026-04-18T15:01:37+00:00",
"owner_type": "merchant_key",
"owner_id": 1,
"data": {
"transaction_id": "TXN-1234567890-12ab",
"session_id": "cs_a1b2c3d4e5f6...",
"amount": 250.00,
"order_id": "yuri-1930",
"status": "confirmed",
"sender_username": "johnpayment",
"sender_id": 69
}
}
Webhook Events
| Event | Fired when |
|---|---|
| transaction.confirmed | Payment completed, confirmed and funds are now in your account. |
| transaction.pending | Payment created but held for manual review (large transactions). Funds are not yet in your account. |
| transaction.reverted | A previously confirmed or pending transaction was reversed or rejected. Funds have been returned to the sender. |
| test.ping | Sent when you click "Test" in the Merchant Panel. Use this to verify your endpoint is reachable. This event is always delivered regardless of your event subscriptions. |
| subscription.created | A customer subscribed to one of your plans. |
| subscription.renewed | Subscription renewed and payment was collected for the new billing period. |
| subscription.cancelled | Subscription was cancelled. Check data.reason: user_cancelled (customer requested), period_ended (cancellation took effect), merchant_immediate or merchant_end_of_period (merchant cancelled). |
| subscription.past_due | Payment failed on a subscription. Retrying automatically. |
| subscription.expired | Subscription expired after all payment retries were exhausted. |
| subscription.resumed | Customer undid a pending cancellation. |
| invoice.paid | Subscription invoice was paid successfully. |
| invoice.payment_failed | Subscription invoice payment attempt failed. Will retry automatically. |
Signature Verification
Every webhook request includes an X-MPI-Signature header containing an
HMAC-SHA256 hash of the request body, signed with your endpoint's signing secret.
Always verify this signature before processing the webhook to ensure it came from MyPayIndia.
Node.js
const crypto = require('crypto');
app.post('/webhooks/mypayindia', (req, res) => {
const signature = req.headers['x-mpi-signature'];
const secret = 'your_webhook_signing_secret';
const expected = crypto
.createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
if (signature !== expected) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
// Process the event
res.status(200).send('OK');
});
Python
import hmac
import hashlib
def handle_webhook(request):
raw_body = request.get_data()
signature = request.headers.get('X-MPI-Signature', '')
secret = b'your_webhook_signing_secret'
expected = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
return 'Invalid signature', 401
event = request.get_json()
# Process the event
return 'OK', 200
PHP
$raw_body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_MPI_SIGNATURE'] ?? '';
$secret = 'your_webhook_signing_secret';
$expected = hash_hmac('sha256', $raw_body, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($raw_body, true);
// Process the event
http_response_code(200);
Important: Compute the HMAC against the raw request body, not a re-serialized version. Re-encoding JSON may change formatting and produce a different hash.
Subscriptions
MyPayIndia supports recurring billing through subscription plans. You create plans with a fixed amount and billing interval, then use the same checkout flow to subscribe customers. MyPayIndia handles billing automatically, charging the customer's balance on each cycle and notifying you via webhooks.
Plans are created and managed from the Merchant Panel in your merchant key's settings page. The API provides read access to plans and full management of subscriptions and invoices.
Plans
A plan defines the pricing and billing interval for a subscription. Plans are created in the Merchant Panel.
Plans have a fixed amount, a billing interval (daily, weekly,
monthly, or yearly), an interval count (e.g. every 3 months),
and an optional trial period in days. Once created, the amount and interval cannot be changed -
create a new plan instead.
Deactivating a plan prevents new subscriptions but does not affect existing subscribers. Plans with active subscriptions cannot be deleted.
Retrieve Plans
| Parameter | Type | Description |
|---|---|---|
| id | integer optional | Get a specific plan by ID |
| include_inactive | string optional | Set to true to include deactivated plans |
Example Response
{
"success": true,
"data": [
{
"id": 6,
"name": "MyPayIndia Premium",
"amount": 25000,
"interval": "monthly",
"interval_count": 1,
"trial_period_days": 7,
"active": true,
"created_at": "2026-06-04T10:37:55+00:00"
}
]
}
25000 = 250.00 INR.Subscription Checkout
To subscribe a customer, create a checkout session with a plan_id instead of
an amount. The rest of the flow is identical to one time payments.
Request Body
| Parameter | Type | Description |
|---|---|---|
| plan_id | integer required | The plan to subscribe the customer to. Cannot be combined with amount. |
| return_url | string required | URL to redirect the customer to after checkout. |
| order_id | string optional | Your order reference (max 64 characters). |
Example Request
curl -X POST https://mypayindia.com/api/v2/pay/create \
-H "Authorization: Bearer sk_live_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"plan_id": 6,
"return_url": "https://mycoolandawesomewebsite.com/i-get-paid/i-get-paid-consistently",
"order_id": "yuri-1931"
}'
Example Response
{
"success": true,
"data": {
"session_id": "ps_a1b2c3d4e5f6...",
"checkout_url": "https://mypayindia.com/pay?session=ps_a1b2c3d4e5f6...",
"expires_at": "2026-06-04T15:30:00+00:00",
"plan_id": 6,
"mode": "subscription"
}
}
What happens at checkout
- Plan without trial: Customer is charged immediately. Subscription starts as
active. - Plan with trial: No charge at checkout. Subscription starts as
trialing. First charge happens when the trial ends. - If the customer already has an active subscription to this plan, checkout is blocked.
GET /api/v2/pay/status) includes mode,
plan_id, subscription_id, and subscription_status
for subscription sessions.
Manage Subscriptions
List Subscriptions
| Parameter | Type | Description |
|---|---|---|
| id | string optional | Get a specific subscription by its sub_xxx ID |
| status | string optional | Filter by status: active, trialing, past_due, cancelled, expired |
| plan_id | integer optional | Filter by plan |
Example Response
{
"success": true,
"data": [
{
"subscription_id": "sub_a1b2c3d4e5f6...",
"plan_id": 6,
"plan_name": "MyPayIndia Premium",
"status": "active",
"payer_username": "johnpayment",
"payer_id": 123,
"amount": 25000,
"interval": "monthly",
"interval_count": 1,
"cancel_at_period_end": false,
"current_period_start": "2026-06-04T10:00:00+00:00",
"current_period_end": "2026-07-04T10:00:00+00:00",
"cancelled_at": null,
"created_at": "2026-06-04T10:00:00+00:00"
}
]
}
Cancel a Subscription
By default, the subscription remains active until the end of the current billing period.
Pass {"immediate": true} in the request body to cancel immediately.
Example Response
{
"success": true,
"data": {
"subscription_id": "sub_a1b2c3d4e5f6...",
"status": "active",
"cancel_at_period_end": true
}
}
Invoices
Every billing cycle creates an invoice. Invoices track the payment status for each period.
Example Response
{
"success": true,
"data": [
{
"invoice_id": "inv_a1b2c3d4e5f6...",
"amount": 25000,
"status": "paid",
"transaction_id": "TXN-1234567890-12ab",
"attempts": 1,
"period_start": "2026-06-04T10:00:00+00:00",
"period_end": "2026-07-04T10:00:00+00:00",
"paid_at": "2026-06-04T10:00:00+00:00",
"created_at": "2026-06-04T10:00:00+00:00"
}
]
}
Invoice Statuses
| Status | Meaning |
|---|---|
| paid | Payment successful. Funds are in your account. |
| pending | Payment failed but retrying. Check attempts for retry count. |
| failed | All retry attempts exhausted. Payment was not collected. |
| void | Invoice was voided (subscription expired before payment could be collected). |
Subscription Lifecycle
Statuses
| Status | Meaning |
|---|---|
| trialing | In free trial period. No charges until trial ends. |
| active | Subscription is active and billing normally. |
| past_due | Payment failed. MyPayIndia is retrying with exponential backoff (1h, 2h, 4h, 8h). The subscription remains in this state during retries. |
| cancelled | Subscription was cancelled by the customer or merchant. |
| expired | All payment retries exhausted. Subscription is terminated. |
Billing Behavior
MyPayIndia automatically charges the customer's account balance at the start of each billing period. If the charge fails (insufficient balance), the invoice enters a retry cycle with exponential backoff. After 5 failed attempts (~15 hours), the invoice is marked as failed and the subscription is expired.
Cancellation
When a subscription is cancelled at period end (cancel_at_period_end: true),
it remains active and functional until the current period ends. No further charges are made.
The customer can undo a pending cancellation before the period ends.
Immediate cancellation terminates the subscription right away. This is only available via the merchant API or Merchant Panel, not to customers directly.
API Reference
All API requests are authenticated via the Authorization: Bearer sk_live_... header. All request and response bodies are JSON. Timestamps are in ISO 8601 format.
Checkout
Create a checkout session. Pass amount for one-time payments or plan_id for subscriptions. See Create Session and Subscription Checkout for full details.
Get the status of a checkout session. See Verify Payment above for full details.
Webhook Endpoints
Create a webhook endpoint. Body: url (string), events (array of event strings), description (optional string). Returns the endpoint with a signing secret shown once.
List all your webhook endpoints.
Get a single webhook endpoint by ID.
Update a webhook endpoint. Body can include: url, events, description, active (boolean).
Delete a webhook endpoint. Also deletes all delivery history for this endpoint.
Rotate the signing secret for an endpoint. Returns the new secret once. The old secret stops working immediately.
Webhook Deliveries
List recent deliveries for a webhook endpoint. Optional: limit (default 50, max 200).
Manually redeliver a specific webhook delivery. Resets the retry counter.
Plans
List all active plans. Pass ?id=6 for a specific plan, or ?include_inactive=true to include deactivated plans.
Subscriptions
List all subscriptions. Optional filters: ?status=active, ?plan_id=6. Pass ?id=sub_xxx for a specific subscription.
Cancel a subscription. By default cancels at end of billing period. Pass {"immediate": true} in the request body to cancel immediately. The subscriber is notified by email.
Invoices
List all invoices for a subscription. Returns payment status, transaction ID, retry attempts, and billing period for each invoice.
