Nexus documentation

Integrate once and accept payments through any provider connected to your workspace.

Quickstart

  1. Create an organization and an application in the dashboard.
  2. Connect a provider (start with the Nexus Mock Provider in sandbox).
  3. Create a sandbox API key for the application — it is shown only once.
  4. Add a webhook endpoint and store its signing secret.
  5. Create a payment, then confirm its final status through webhooks.
Create a payment
curl https://api.nexus.example/v1/payments \
  -H "Authorization: Bearer nx_test_..." \
  -H "Idempotency-Key: order_123" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000,
    "currency": "USD",
    "payment_method": "mobile_money",
    "customer": { "external_id": "customer_123", "email": "customer@example.com" },
    "metadata": { "order_id": "order_123" }
  }'
Response · 201 Created
{
  "id": "pay_Qm41x8...",
  "application_id": "app_koda...",
  "provider": "mock",
  "provider_reference": "mock_7f2k...",
  "status": "pending",
  "amount": 1000,
  "currency": "USD",
  "next_action": {
    "type": "await_customer_confirmation",
    "instructions": "Approve the prompt on your phone"
  },
  "created_at": "2026-10-24T12:00:00Z"
}

Authentication

Authenticate with a secret API key in the Authorization: Bearer header. Keys are scoped to one application and one environment: nx_test_ for sandbox and nx_live_ for production. Never expose secret keys in browsers or mobile apps.

Amounts & currencies

Amounts are integers in the currency’s minor unit. 1000 USD is $10.00, while 1000 XAF is 1,000 FCFA (zero-decimal). Values with more precision than the currency allows are rejected. When you pay for a catalog price, send price_id; the amount is resolved by Nexus and any client-sent amount is ignored.

Idempotency

Send an Idempotency-Key header on every POST. Retrying with the same key and the same body returns the original response without creating a second charge. Reusing a key with a different body returns 409 idempotency_conflict. Keys are retained for 24 hours.

API reference

POST/v1/payments
GET/v1/payments
GET/v1/payments/:id
POST/v1/payments/:id/cancel
GET/v1/providers
GET/v1/payment-methods
POST/v1/customers
GET/v1/customers/:id
GET/v1/products
GET/v1/prices
POST/v1/subscriptions
GET/v1/subscriptions/:id
POST/v1/subscriptions/:id/cancel
GET/v1/events

In development, the interactive OpenAPI reference is served by the API at /docs.

Payment statuses

createdpendingrequires_actionprocessingsucceededfailedcancelledexpiredrefundedpartially_refunded

Terminal states (failed, cancelled, expired, refunded) never change. Out-of-order provider callbacks that would move a payment backwards are recorded but not applied.

Webhooks

Nexus delivers events to the endpoints of the application that owns the resource. Deliveries are at-least-once: always deduplicate on event.id. Failed deliveries are retried with exponential backoff (1 min, 5 min, 30 min, 2 h, 8 h) before moving to the dead-letter queue, from which you can retry manually.

Signature scheme
Nexus-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>

signed_payload = t + "." + raw_request_body
v1 = HMAC_SHA256(endpoint_secret, signed_payload)
Express consumer
import { verifyNexusSignature } from "@nexus/sdk/webhooks";

app.post("/webhooks/nexus", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = verifyNexusSignature({
      payload: req.body,                       // raw Buffer, not parsed JSON
      header: req.header("Nexus-Signature")!,
      secret: process.env.NEXUS_WEBHOOK_SECRET!,
      toleranceSeconds: 300,                   // replay protection
    });
  } catch {
    return res.status(400).send("Invalid signature");
  }

  // Deliveries are at-least-once: deduplicate on event.id.
  if (await db.processedEvents.exists(event.id)) return res.sendStatus(200);

  if (event.type === "payment.succeeded") await fulfillOrder(event.data);
  await db.processedEvents.insert(event.id);
  res.sendStatus(200);
});

Event types

payment.createdpayment.pendingpayment.requires_actionpayment.processingpayment.succeededpayment.failedpayment.cancelledpayment.expiredpayment.refundedsubscription.createdsubscription.renewedsubscription.past_duesubscription.cancelledsubscription.expiredinvoice.createdinvoice.paidinvoice.payment_failedcustomer.created

TypeScript SDK

@nexus/sdk
pnpm add @nexus/sdk

import { Nexus } from "@nexus/sdk";

const nexus = new Nexus({ apiKey: process.env.NEXUS_API_KEY! });

const payment = await nexus.payments.create(
  {
    amount: 1000,
    currency: "USD",
    paymentMethod: "mobile_money",
    customer: { externalId: "customer_123" },
  },
  { idempotencyKey: "order_123" },
);

const latest = await nexus.payments.retrieve(payment.id);

Testing with the mock provider

The Nexus Mock Provider is deterministic and never moves money. Choose an outcome with metadata.mock_scenario:

succeedfailpendingdelayed_callbackduplicate_callbacktimeout