Developers
Integrate once. Add gateways later.
A single REST API and one webhook contract sit in front of every provider you connect. Swapping or adding a gateway is a configuration change, not a sprint.
Quickstart
Your first payment in two calls
Create a payment with smart routing enabled, and OrcoPay picks the gateway, handles the retry and reports a single final status.
pip install requestsBefore you start
- 1Create an account and generate a sandbox key
- 2Connect at least one gateway in test mode
- 3Set a webhook endpoint to receive final statuses
import os
import requests
payment = requests.post(
"https://api.orcopay.com/v1/payments",
headers={
"Authorization": f"Bearer {os.environ['ORCOPAY_API_KEY']}",
"Idempotency-Key": "ord_9F42B7",
},
json={
"amount": 1840000, # minor units
"currency": "INR",
"method": "card",
"routing": "smart", # or a named rule set
"reference": "ord_9F42B7",
"customer": {
"id": "cus_71bd",
"email": "asha@example.com",
},
},
).json()
print(payment["status"]) # "authorized"
print(payment["gateway"]) # "razorpay"
print(payment["attempts"]) # 2API
Designed to be boring in production
The interesting behaviour belongs in the routing engine, not in your integration code.
Idempotent by default
Every write accepts an idempotency key. Replaying a request returns the original result instead of creating a second payment.
Provider-agnostic shapes
Requests and responses look the same regardless of which gateway handled the transaction. Raw provider payloads stay available.
Predictable errors
Stable machine-readable error codes, with the retry semantics documented per code rather than inferred from a message string.
Versioned, never broken
Breaking changes ship behind a dated API version. Your integration keeps working until you choose to move.
Webhooks
One event schema, whatever the gateway
Every provider names things differently. OrcoPay normalizes them, signs the payload, and keeps retrying until you acknowledge it.
{
"id": "evt_4c81f0aa27",
"type": "payment.authorized",
"created": "2026-08-26T09:14:22Z",
"data": {
"payment": {
"id": "pay_8f2ad41c93",
"reference": "ord_9F42B7",
"amount": 1840000,
"currency": "INR",
"status": "authorized",
"gateway": "razorpay",
"attempts": [
{ "gateway": "payu", "status": "declined", "code": "issuer_timeout" },
{ "gateway": "razorpay", "status": "authorized", "code": null }
]
}
}
}import { verifyWebhook } from "@orcopay/node";
app.post("/webhooks/orcopay", express.raw({ type: "*/*" }), (req, res) => {
const event = verifyWebhook({
payload: req.body,
signature: req.header("OrcoPay-Signature"),
secret: process.env.ORCOPAY_WEBHOOK_SECRET,
toleranceSeconds: 300,
});
if (event.type === "payment.authorized") {
fulfil(event.data.payment.reference);
}
res.sendStatus(204); // ack fast; OrcoPay retries anything non-2xx
});Event types
- payment.authorized
- payment.captured
- payment.failed
- payment.refunded
- payout.settled
- dispute.opened
- dispute.resolved
- gateway.degraded
Server SDKs
Typed clients with retries, idempotency and webhook verification built in.
- Node.js
@orcopay/node - Python
orcopay - PHP
orcopay/orcopay-php - Go
github.com/orcopay/orcopay-go
Sandbox and status
The sandbox mirrors production routing, including the failure modes you need to handle.
- Simulate soft declines, hard declines and issuer timeouts
- Force a specific gateway to fail and watch the cascade
- Replay any webhook against a local tunnel
- Per-provider uptime and latency published continuously
Start in the sandbox
Generate a test key, connect a simulated gateway, and put a payment through the full cascade in a few minutes.
No setup fees · Sandbox access on signup
