NavenDocs
NavenDocs
Introduction
OverviewQuickstartIntegration GuideAPI ReferenceFulfillment and Recovery
Back to Naven Network
Payment Intents

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 session

Your backend should:

  1. Authenticate the application user.
  2. Create a local pending order with a unique order ID.
  3. Create the Naven intent using that order ID as externalId.
  4. Store the returned Naven intent ID on the local order.
  5. 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_KEY in 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 externalId on retries.
  • Return only the public paymentUrl to 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 pending and settling as retryable states.
  • Test the complete flow with the minimum supported amount (100 USD cents) before enabling production purchases.

Quickstart

Create a Merchant and accept your first Naven Payment Intent.

API Reference

Project, Merchant, and Payment Intent endpoints.

On this page

Backend configurationBackend clientRecommended application endpointFrontend payment flowBackend confirmationProduction checklist