Every response carries "sandbox": true for simulated payments, false when a live provider processed real money.
Authentication
Send your credentials on every request over HTTPS. Generate them under API Keys & Webhooks. Limit: 60 requests/minute per key.
X-Api-Key: pk_sandbox_... X-Api-Secret: sk_sandbox_... Content-Type: application/json
POST /payin/create
Creates a pending payment and returns a checkout_url to send your customer to. Send an optional Idempotency-Key header (8–64 chars) to safely retry: the same key returns the original transaction.
curl -X POST https://jupiterpay.business/api/v1/payin/create \
-H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET" \
-H "Idempotency-Key: order-1001-attempt-1" \
-H "Content-Type: application/json" \
-d '{
"amount": "1000.00",
"currency": "INR",
"order_id": "order-1001",
"customer": { "name": "Asha Rao", "email": "asha@example.com", "phone": "+919800000000" },
"callback_url": "https://merchant.example/webhooks/paygrid",
"return_url": "https://merchant.example/orders/1001"
}'| Field | Type | Notes |
|---|---|---|
amount | string | Decimal, max 2 places, 1.00 – 1000000.00 |
currency | string | INR |
order_id | string | Your reference; unique per merchant (≤64, [A-Za-z0-9_.-:]) |
customer | object | name, email required; phone optional |
callback_url | string? | Server-to-server status notifications (overrides account webhook URL) |
return_url | string? | Where the customer can go after seeing the result |
Response 201:
{
"sandbox": true,
"transaction_id": "txn_4f1c...",
"order_id": "order-1001",
"status": "PENDING",
"amount": "1000.00",
"currency": "INR",
"fee": "50.00",
"net_amount": "950.00",
"checkout_url": "https://jupiterpay.business/pay/txn_4f1c...",
"failure_reason": null,
"created_at": "2026-11-14T10:00:00.000Z",
"completed_at": null
}GET /payin/:transaction_id
Returns the same object. Use it to confirm status server-side before fulfilling an order.
Payment flow
- You call
/payin/create→ transaction is PENDING. - Customer opens
checkout_url→ redirected to the provider's hosted checkout. - Provider sends us a signed webhook. We verify the signature, de-duplicate it, then query the provider directly for the real status and amount.
- Only then: SUCCEEDED (net amount credited to your pending balance) or FAILED. Unpaid payments become EXPIRED after 30 minutes.
- We POST a signed notification to your callback/webhook URL. Settlement later moves funds from pending to available.
Customer browser redirects are never treated as proof of payment — by us or, please, by you.
Webhooks to your server
POST <your callback_url>
X-Gateway-Event-Id: evt_...
X-Gateway-Timestamp: 1763114400
X-Gateway-Signature: hex(HMAC_SHA256(webhook_signing_secret, timestamp + "." + raw_body))
{ "id": "evt_...", "type": "payin.succeeded", "sandbox": true, "data": { ...transaction } }Verify (Node.js):
import crypto from "node:crypto";
function verify(rawBody, headers, secret) {
const ts = headers["x-gateway-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // replay window
const expected = crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const given = Buffer.from(headers["x-gateway-signature"] ?? "");
return given.length === expected.length && crypto.timingSafeEqual(given, Buffer.from(expected));
}Event types: payin.succeeded, payin.failed, payin.expired. Treat id as an idempotency key on your side.
Fees
Payin commission (default 5%) is deducted from each successful payin: ₹1,000 → fee ₹50 → ₹950 credited. Payout commission (default 3%) is deducted from each withdrawal: ₹1,000 → fee ₹30 → ₹970 paid out. Rates are snapshotted on each transaction.
Errors
{ "error": { "code": "validation_error", "message": "Invalid request", "details": { ... } } }| 400 | invalid_json |
| 401 | unauthorized |
| 403 | merchant_suspended |
| 404 | not_found |
| 409 | duplicate_order |
| 422 | validation_error · invalid_amount · unsupported_currency · idempotency_mismatch |
| 429 | rate_limited |
| 502 | provider_error |
| 503 | provider_unavailable |