Developers
One API for every network
Create a payment, show its deposit address, and verify the signed webhook when it is paid. The full guides and the API reference are in the documentation.
$ curl https://api.nowgate.io/v1/payments \
-H "Authorization: Bearer $NOWGATE_API_KEY" \
-H "Idempotency-Key: order-1042" \
-d '{"network": "tron", "asset": "USDT", "amount": "49.90"}'
201 Created
{ "id": "pay_01jc3x6k2t", "status": "waiting",
"deposit": { "address": "TN3W4H6rK5Zs…GaQ1" } }
✓ payment.confirming webhook signed, delivered
✓ payment.finished webhook signed, deliveredThe whole integration
1. Create a payment
The response holds the deposit address to show your customer, and the payment's
id.Request curl https://api.nowgate.io/v1/payments \ -H "Authorization: Bearer $NOWGATE_API_KEY" \ -H "Idempotency-Key: order-1042" \ -H "Content-Type: application/json" \ -d '{ "network": "solana", "asset": "USDC", "amount": "125", "order_id": "1042" }'2. Receive the webhook
When the payment changes, Nowgate POSTs an event to your endpoint, signed in the
Nowgate-Signatureheader.payment.finished (excerpt) { "id": "evt_01jc3x9w4qv7m2c8d5t0y6k1hr", "object": "event", "type": "payment.finished", "api_version": "2026-11-01", "environment": "live", "created_at": "2026-11-05T12:00:00Z", "data": { "object": { "id": "pay_01jc3x6k2tf8yq0b5n2c7m9d4v", "status": "finished", "amount": "125", "amount_received": "125", "version": 4 } } }3. Verify it, then re-check before you ship
Verify the signature over the raw body, deduplicate by event
id, and ignore events older than theversionyou have. Before you ship goods, fetch the payment withGET /v1/payments/{id}and check its status.verify-webhook.ts (Node.js) import { createHmac, timingSafeEqual } from "node:crypto"; export type VerifyError = "malformed" | "no_signature" | "timestamp" | "mismatch"; /** * Verifies a Nowgate-Signature header ("t=<unix seconds>,v1=<hex HMAC-SHA256>") * against the raw request body. Pass the exact bytes you received, before any JSON * parsing, and every endpoint secret you accept (two while rotating). * Returns null when the event is genuine, or the reason it is not. */ export function verifyNowgateSignature( header: string, rawBody: Buffer | string, secrets: string[], { toleranceSeconds = 300, now = Math.floor(Date.now() / 1000) } = {}, ): VerifyError | null { let timestamp = 0; const signatures: Buffer[] = []; for (const part of header.split(",")) { const item = part.trim(); const eq = item.indexOf("="); if (eq < 0) return "malformed"; const key = item.slice(0, eq); const value = item.slice(eq + 1); if (key === "t") { if (timestamp !== 0 || !/^[0-9]+$/.test(value) || Number(value) <= 0) return "malformed"; timestamp = Number(value); } else if (key === "v1") { if (!/^[0-9a-fA-F]{64}$/.test(value)) return "malformed"; signatures.push(Buffer.from(value, "hex")); } // other schemes are ignored } if (timestamp === 0) return "malformed"; if (signatures.length === 0) return "no_signature"; if (Math.abs(now - timestamp) > toleranceSeconds) return "timestamp"; const body = typeof rawBody === "string" ? Buffer.from(rawBody, "utf8") : rawBody; let match = false; for (const secret of secrets) { const expected = createHmac("sha256", secret).update(`${timestamp}.`).update(body).digest(); for (const signature of signatures) { match = timingSafeEqual(expected, signature) || match; // no early exit } } return match ? null : "mismatch"; }This function and its Python twin are tested against the published signature vectors, the same ones the engine's own verifier passes.
Conventions
- Authentication
- Authorization: Bearer with an API key. Keys are per environment: ngk_live_… or ngk_sandbox_…, and carry scopes.
- Idempotency
- Every POST needs an Idempotency-Key. The same key and body replay the original response; the same key with a different body is refused.
- Amounts
- Decimal strings in the asset's units, like "12.5". Never numbers. Extra decimal places are rejected, not rounded.
- Prices
- price_amount and price_currency (USD or EUR) instead of amount: Nowgate converts at its own rate, and the amount holds until the payment expires. GET /v1/estimate shows it first.
- Errors
- RFC 9457 problem details with a stable code, a field pointer where it applies, and a page per code in the docs.
- Pagination
- Lists are newest first and page with starting_after and ending_before cursors.
- Versioning
- Every event names the api_version its body follows, today 2026-11-01.
- Testing
- In the sandbox, POST /v1/sandbox/payments/{id}/simulate plays the payer: every status and webhook without testnet coins.
Errors you can act on
Every error has a code your program can branch on, and a page that explains it.
{
"type": "https://docs.nowgate.io/errors/amount_too_precise",
"title": "Unprocessable Entity",
"status": 422,
"detail": "USDC has 6 decimal places; amounts are never rounded.",
"instance": "/v1/payments",
"code": "amount_too_precise",
"request_id": "req_01jc3xb1n6f0q2w8e4r7t5y9v3",
"errors": [
{
"pointer": "/amount",
"code": "amount_too_precise",
"detail": "USDC has 6 decimal places; amounts are never rounded."
}
]
}SDKs
TypeScript, Python and PHP, generated from the same specification, with retries, pagination, typed errors and webhook verification built in. Read the SDK guides.
On the way
- A sandbox on testnetsComing soon