Skip to main content
Invoices vs orders: These guides walk through invoices (formal billing, line items, publish). The collect flow is the same for orders — use /v1/orders/* instead of /v1/invoices/* and set source_type: "ORDER" on the checkout session. See Orders and Parties & counterparties.Hosted checkout: To skip party, invoice, session, and execute steps entirely, see Hosted checkout.
Fastest path for Next.js: Hosted checkout — create a checkout source server-side and redirect to url. No Stripe or payment-account setup required.
Call the Dollr API from server-only code for API-embedded flows. Never expose your Client Secret in the browser.
API Reference: Obtain token · Collect

Prerequisites

  • Next.js 14+ (App Router)
  • Environment variables in .env.local (not NEXT_PUBLIC_ except Stripe publishable key for card UI):
DOLLR_CLIENT_ID=...
DOLLR_CLIENT_SECRET=...
// app/actions/checkout.ts
"use server";

const BASE = "https://api.heydollr.app";

async function dollrHeaders() {
  const { access_token } = await fetch(`${BASE}/v1/jwt/client/obtain/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.DOLLR_CLIENT_ID,
      client_secret: process.env.DOLLR_CLIENT_SECRET,
    }),
  }).then((r) => r.json());
  return { Authorization: `Bearer ${access_token}`, "Content-Type": "application/json" };
}

export async function createHostedCheckout() {
  const headers = await dollrHeaders();
  const checkout = await fetch(`${BASE}/v1/checkouts/create`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      mode: "HOSTED",
      source_kind: "ORDER",
      party_name: "Amara Kamara",
      party_phone: "231771234567",
      currency: "USD",
      items: [{ name: "T-Shirt", currency: "USD", amount: 25 }],
      success_url: `${process.env.SITE_URL}/checkout/success`,
      cancel_url: `${process.env.SITE_URL}/checkout/cancel`,
    }),
  }).then((r) => r.json());

  return checkout.url; // redirect customer here
}
On your success page, verify server-side with GET /v1/status/source before fulfilling — see Hosted checkout.

Option B — API-embedded collection

1

Route Handler — obtain token

// app/api/dollr/token/route.ts
const BASE = "https://api.heydollr.app";

export async function POST() {
  const res = await fetch(`${BASE}/v1/jwt/client/obtain/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.DOLLR_CLIENT_ID,
      client_secret: process.env.DOLLR_CLIENT_SECRET,
    }),
    cache: "no-store",
  });
  const data = await res.json();
  return Response.json({ access_token: data.access_token });
}
2

Server Action — full collect flow

Follow Quick Start steps: party → counterparty → invoice → publish → session → payment account → execute. Or use Collect with card for Stripe Elements.
3

Poll from a Route Handler

// app/api/dollr/status/[referenceId]/route.ts
export async function GET(
  _req: Request,
  context: { params: Promise<{ referenceId: string }> }
) {
  const { referenceId } = await context.params;
  const headers = await dollrHeaders();
  const res = await fetch(
    `https://api.heydollr.app/v1/status/collection/${referenceId}`,
    { headers, cache: "no-store" }
  );
  return Response.json(await res.json());
}
Do not expose DOLLR_CLIENT_SECRET to Client Components. Use Route Handlers or Server Actions only.

Try it yourself

Hosted checkout

Mobile money and card on a Dollr-hosted page — fastest path.

Orders

Same collect flow with source_type: ORDER and /v1/orders/*.

Direct checkout

One API call to create source — API-embedded flow.
Last modified on June 23, 2026