Skip to content
Nowgate

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, delivered

The whole integration

  1. 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. 2. Receive the webhook

    When the payment changes, Nowgate POSTs an event to your endpoint, signed in the Nowgate-Signature header.

    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. 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 the version you have. Before you ship goods, fetch the payment with GET /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.

422 application/problem+json
{
  "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