Nexus documentation
Integrate once and accept payments through any provider connected to your workspace.
Quickstart
- Create an organization and an application in the dashboard.
- Connect a provider (start with the Nexus Mock Provider in sandbox).
- Create a sandbox API key for the application — it is shown only once.
- Add a webhook endpoint and store its signing secret.
- Create a payment, then confirm its final status through webhooks.
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" }
}'{
"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 | Create a payment (Idempotency-Key header required) |
| GET | /v1/payments | List payments |
| GET | /v1/payments/:id | Retrieve a payment |
| POST | /v1/payments/:id/cancel | Cancel when the provider and state allow it |
| GET | /v1/providers | Providers enabled for this application |
| GET | /v1/payment-methods | Payment methods available for a currency/country |
| POST | /v1/customers | Create a customer |
| GET | /v1/customers/:id | Retrieve a customer |
| GET | /v1/products | List products |
| GET | /v1/prices | List prices |
| POST | /v1/subscriptions | Create a subscription |
| GET | /v1/subscriptions/:id | Retrieve a subscription |
| POST | /v1/subscriptions/:id/cancel | Cancel now or at period end |
| GET | /v1/events | List normalized events |
In development, the interactive OpenAPI reference is served by the API at /docs.
Payment statuses
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.
Nexus-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>
signed_payload = t + "." + raw_request_body
v1 = HMAC_SHA256(endpoint_secret, signed_payload)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.createdTypeScript 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: