Integration Guide
Recommended backend and frontend integration for Naven Payment Intents.
This guide uses a credits top-up as the example, but the same flow works for orders, subscriptions, API access, and agent provisioning.
Backend configuration
NAVEN_API_URL=https://api.naven.network
NAVEN_API_KEY=naven_api_...
NAVEN_MERCHANT_ID=...The Project API key must be available only to the backend.
Backend client
type PaymentIntent = {
id: string;
merchantId: string;
externalId: string;
customerRef: string | null;
status: "pending" | "settling" | "settled" | "failed" | "expired";
amountUsdCents: number;
currency: "USD";
paymentUrl: string;
transactionHash: string | null;
failureCode: string | null;
failureMessage: string | null;
expiresAt: string;
settledAt: string | null;
};
const navenApiUrl = process.env.NAVEN_API_URL!;
const navenApiKey = process.env.NAVEN_API_KEY!;
const merchantId = process.env.NAVEN_MERCHANT_ID!;
async function navenRequest<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${navenApiUrl}${path}`, {
...init,
headers: {
Authorization: `Bearer ${navenApiKey}`,
"Content-Type": "application/json",
...init?.headers,
},
});
const body = await response.json();
if (!response.ok) {
throw new Error(`${body.code ?? response.status}: ${body.message ?? "Naven request failed"}`);
}
return body as T;
}
export function createNavenPaymentIntent(input: {
orderId: string;
customerId: string;
amountUsdCents: number;
}) {
return navenRequest<PaymentIntent>("/v1/payment-intents", {
method: "POST",
body: JSON.stringify({
merchantId,
amountUsdCents: input.amountUsdCents,
externalId: input.orderId,
customerRef: input.customerId,
description: "Workspace credit top-up",
}),
});
}
export function getNavenPaymentIntent(intentId: string) {
return navenRequest<PaymentIntent>(
`/v1/payment-intents/${encodeURIComponent(intentId)}`,
);
}Recommended application endpoint
Your frontend should call your own backend, not Naven's private endpoint:
POST /api/topups
Authorization: your application sessionYour backend should:
- Authenticate the application user.
- Create a local pending order with a unique order ID.
- Create the Naven intent using that order ID as
externalId. - Store the returned Naven intent ID on the local order.
- Return only the intent ID,
paymentUrl, amount, and expiry to the browser.
Frontend payment flow
const topup = await fetch("/api/topups", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amountUsdCents: 9900 }),
}).then(response => response.json());
// x402Fetch performs the initial request, signs the 402 requirement with the
// connected wallet, and retries with PAYMENT-SIGNATURE.
const paymentResponse = await x402Fetch(topup.paymentUrl, {
method: "POST",
});
if (!paymentResponse.ok) {
throw new Error("Payment did not settle");
}
// Treat this as immediate UX feedback only. Ask your backend to confirm and
// fulfill the order from Naven's authenticated status endpoint.
await fetch(`/api/topups/${topup.orderId}/confirm`, { method: "POST" });Build x402Fetch using the exact v2 flow in the
Buyer/Client Guide. With Privy or Wagmi,
register the connected EVM wallet as the exact scheme signer.
Backend confirmation
The confirmation endpoint queries Naven using the Project API key. If the
intent is settled, update the local order and grant value in one database
transaction. If it is still pending or settling, return a retryable status
without granting value.
Never trust a transaction hash or settlement body supplied only by the browser.
Production checklist
- Keep
NAVEN_API_KEYin backend secrets only. - Keep one stable Merchant ID per isolated receiving configuration.
- Create the local order before or in the same workflow as the Naven intent.
- Reuse the local order ID as
externalIdon retries. - Return only the public
paymentUrlto the browser. - Sign the exact x402 requirement returned by Naven.
- Grant value only after an authenticated backend status query returns
settled. - Make local fulfillment idempotent with a unique Naven intent ID.
- Treat
pendingandsettlingas retryable states. - Test the complete flow with the minimum supported amount (
100USD cents) before enabling production purchases.