— API Reference · v1
Finglo Payments API
Public API for creating orders, charging cards, checking status, and issuing refunds.
Base URL
https://api.finglo.net
Auth
X-Api-Key: <publishable key>
Content type
application/json
Conventions#
Money#
amount is a decimal in major units (e.g. 1500.00 INR). currency is ISO‑4217.
Timestamps#
createdAt, updatedAt, expiresAt are integer epoch milliseconds.
IDs#
All id fields (orders, transactions, refunds, customers, MIDs) are ULIDs.
Statuses#
The status field on Orders, Transactions, and Refunds uses the same enum:
| Status | Meaning |
|---|---|
CREATED | Resource created, not yet in a terminal state |
SUCCESS | Terminal — payment / refund succeeded |
FAILED | Terminal — payment / refund failed |
EXPIRED | Terminal — order's expiresAt passed before payment |
Errors#
All errors share the same envelope:
{
"code": "DUPLICATE_MERCHANT_REF",
"message": "An existing order already uses this merchantRef.",
"details": { "merchantRef": "ORD-1777135255" }
}| HTTP | code | When |
|---|---|---|
400 | INVALID_REQUEST | Malformed JSON, missing required field, bad types |
400 | INVALID_FIELD | Specific field-level validation failed |
401 | UNAUTHORIZED | Missing or invalid X-Api-Key |
403 | FORBIDDEN | Key lacks permission for this resource |
404 | NOT_FOUND | Order / transaction / refund not found |
409 | DUPLICATE_MERCHANT_REF | An existing order already uses this merchantRef |
422 | ORDER_EXPIRED | Transaction attempted after order.expiresAt |
422 | ORDER_ALREADY_PAID | Transaction attempted on an order in SUCCESS state |
422 | ORDER_NOT_REFUNDABLE | Refund on an unpaid / failed / expired / already-refunded order |
500 | INTERNAL_ERROR | Unhandled server error |
End-to-end flow#
1. POST /orders → orderId, status=CREATED
2. POST /transactions (orderId, card) → paymentUrl, pspCode
3. Open paymentUrl, drive the page (2D / 3D OTP / DUMMY approve|decline)
4. Poll GET /orders/{orderId}/status → SUCCESS | FAILED | EXPIRED
5. (optional) POST /orders/{orderId}/refund if status == SUCCESS1. Create Order#
/api/v1/ordersCreates an order that a transaction can later be attached to.
Request#
| Field | Type | Required | Notes |
|---|---|---|---|
merchantRef | string | yes | Unique on the merchant side. Reusing returns 409. |
amount | number | yes | Decimal, major units, > 0. |
currency | string | yes | ISO‑4217 (e.g. INR). |
customer.name | string | yes | |
customer.email | string | yes | |
customer.phone | string | no | E.164 recommended. |
redirectUrl | string | no | Where the user lands after the payment page completes. |
webhookUrl | string | no | Receives status-change POSTs (see Webhooks). |
description | string | no | |
metadata | object | no | Free-form key/value. |
Example#
curl https://api.finglo.net/api/v1/orders \
-H "Content-Type: application/json" \
-H "X-Api-Key: $FINGLO_API_KEY" \
-d '{
"merchantRef": "ORD-1777135255",
"amount": 1500.00,
"currency": "INR",
"customer": {
"name": "Rahul Sharma",
"email": "rahul@example.com",
"phone": "+91-9988776655"
},
"redirectUrl": "https://www.google.com",
"webhookUrl": "https://merchant-site.com/api/webhook",
"description": "Premium Plan Subscription"
}'Response — 201 Created#
{
"id": "01KQ2W5V82FX9HNWJ14ESEE1PJ",
"merchantId": "01KPX7N2TY3NXVEWE5ARKXX0TR",
"merchantRef": "ORD-1777135255",
"amount": 1500.00,
"currency": "INR",
"customer": {
"id": "01KPX7WNTS6X1GD0V8DWD4EKQP",
"name": "Rahul Sharma",
"email": "rahul@example.com",
"phone": "+91-9988776655"
},
"status": "CREATED",
"redirectUrl": "https://www.google.com",
"webhookUrl": "https://merchant-site.com/api/webhook",
"description": "Premium Plan Subscription",
"metadata": { "plan": "premium", "billing_cycle": "monthly" },
"createdAt": 1777139379458,
"updatedAt": 1777139379458,
"expiresAt": 1777225779458
}Errors#
400 INVALID_REQUEST/INVALID_FIELD401 UNAUTHORIZED409 DUPLICATE_MERCHANT_REF
2. Create Transaction#
/api/v1/transactionsAttaches a card to an order and returns a paymentUrl. Open the URL in a browser to complete payment. Depending on the PSP that the dashboard's routing rules selected, the page is one of:
- 2D — redirects on its own, no user action required
- 3D — OTP page; user submits OTP
- DUMMY — Approve / Decline buttons (test environment only)
After driving the page, poll GET /api/v1/orders/{orderId}/status until terminal.
Request#
| Field | Type | Required | Notes |
|---|---|---|---|
orderId | string | yes | From the order response. |
paymentMethod | string | yes | CARD |
card.cardNumber | string | yes | |
card.expMonth | string | yes | "01" – "12" |
card.expYear | string | yes | 4-digit year |
card.cvv | string | yes | |
card.cardHolderName | string | yes |
Example#
curl https://api.finglo.net/api/v1/transactions \
-H "Content-Type: application/json" \
-H "X-Api-Key: $FINGLO_API_KEY" \
-d '{
"orderId": "01KQ2W5V82FX9HNWJ14ESEE1PJ",
"paymentMethod": "CARD",
"card": {
"cardNumber": "4111111111111111",
"expMonth": "12",
"expYear": "2028",
"cvv": "123",
"cardHolderName": "Rahul Sharma"
}
}'Response — 201 Created#
{
"id": "01KQ2W8732BYXSKYBKKH3KD1JT",
"orderId": "01KQ2W5V82FX9HNWJ14ESEE1PJ",
"merchantId": "01KPX7N2TY3NXVEWE5ARKXX0TR",
"amount": 1500.00,
"currency": "INR",
"paymentMethod": "CARD",
"paymentDetails": {
"last4": "1111",
"card_number": "4111111111111111",
"cvv": "123",
"card_network": "VISA",
"card_holder_name": "Rahul Sharma",
"exp_month": "12",
"exp_year": "2028"
},
"pspCode": "DUMMY",
"midId": "01KQ2R28H6W10XGXA927C9MCEH",
"pspTransactionId": null,
"status": "CREATED",
"errorCode": null,
"errorMessage": null,
"createdAt": 1777139457122,
"updatedAt": 1777139457122,
"paymentUrl": "https://api.finglo.net/pay/01KQ2W8732BYXSKYBKKH3KD1JT"
}pspCode tells you which PSP routing landed on. paymentUrl is the page to drive.
Errors#
400 INVALID_REQUEST/INVALID_FIELD401 UNAUTHORIZED404 NOT_FOUND—orderIddoesn't exist422 ORDER_EXPIRED/ORDER_ALREADY_PAID
3. Get Order Status#
/api/v1/orders/{orderId}/statusReturns the current state of the order. Poll this until status is terminal.
Recommended polling schedule#
| Window | Interval |
|---|---|
| 0 – 1 min | 10 s |
| 1 – 6 min | 60 s |
| 6 – 36 min | 5 min |
| 36 min – 2 hr | 30 min |
Stop on SUCCESS, FAILED, or EXPIRED.
Example#
curl https://api.finglo.net/api/v1/orders/01KQ2W5V82FX9HNWJ14ESEE1PJ/status \
-H "X-Api-Key: $FINGLO_API_KEY"Response — 200 OK#
{
"id": "01KQ2W5V82FX9HNWJ14ESEE1PJ",
"merchantId": "01KPX7N2TY3NXVEWE5ARKXX0TR",
"merchantRef": "ORD-1777135255",
"amount": 1500.00,
"currency": "INR",
"customer": {
"id": "01KPX7WNTS6X1GD0V8DWD4EKQP",
"name": "Rahul Sharma",
"email": "rahul@example.com",
"phone": "+91-9988776655"
},
"status": "SUCCESS",
"redirectUrl": "https://www.google.com",
"webhookUrl": "https://merchant-site.com/api/webhook",
"description": "Premium Plan Subscription",
"metadata": { "plan": "premium", "billing_cycle": "monthly" },
"createdAt": 1777139379458,
"updatedAt": 1777139486752,
"expiresAt": 1777225779458
}Errors#
401 UNAUTHORIZED404 NOT_FOUND
4. Refund Order#
/api/v1/orders/{orderId}/refundInitiates a full refund for the order's successful transaction. Partial refunds are not supported. No request body.
Example#
curl -X POST https://api.finglo.net/api/v1/orders/01KQ2W5V82FX9HNWJ14ESEE1PJ/refund \
-H "X-Api-Key: $FINGLO_API_KEY"Response — 201 Created#
{
"id": "01KQ2X3PRN09WDB7TVMWK17DZG",
"transactionId": "01KQ2W8732BYXSKYBKKH3KD1JT",
"orderId": "01KQ2W5V82FX9HNWJ14ESEE1PJ",
"merchantId": "01KPX7N2TY3NXVEWE5ARKXX0TR",
"midId": "01KQ2R28H6W10XGXA927C9MCEH",
"requestedAmount": 1500.00,
"refundedAmount": null,
"currency": "INR",
"status": "CREATED",
"pspRefundId": "DUMMY_PENDING_01KQ2X3PRN09WDB7TVMWK17DZG",
"reason": null,
"errorCode": null,
"errorMessage": null,
"createdAt": 1777140357909,
"updatedAt": 1777140357909
}refundedAmount is null until the refund reaches SUCCESS.
Errors#
401 UNAUTHORIZED404 NOT_FOUND422 ORDER_NOT_REFUNDABLE— order is unpaid, failed, expired, or already refunded
Webhooks#
If webhookUrl was supplied on order creation, Finglo POSTs to it on every status change. The body is the same shape as the response from GET /api/v1/orders/{orderId}/status.
The endpoint should:
- accept
POST application/json - return any 2xx within 10s — non-2xx responses are retried with exponential backoff
- be idempotent (the same status update may arrive more than once)