HeadlessDomains Partners

Partner developer docs

Copy-paste frontend guide

Use these snippets in a downstream white-label registrar. Keep the API key on the server.

Environment

HEADLESSDOMAINS_PARTNER_API_BASE=https://partners.headlessdomains.com
HEADLESSDOMAINS_PARTNER_API_KEY=hdpart_...
DEFAULT_NAMESPACE=defiwallet
REGISTRAR_BASE_URL=https://my.partnerbrand.com

Server helper

This minimal TypeScript helper is safe for Next.js, Express, Remix, Astro server routes, or any backend that supports `fetch`.

type PreflightResponse = {
  available: boolean;
  domain: string;
  wholesale_price_usd: number;
  wholesale_price_gems: number;
  partner_balance_gems: number;
  can_provision: boolean;
};

type RegistrationResponse = {
  success: boolean;
  registration_id: string;
  domain: string;
  status: string;
  debited_gems: number;
  partner_balance_gems: number;
};

const apiBase = process.env.HEADLESSDOMAINS_PARTNER_API_BASE!;
const apiKey = process.env.HEADLESSDOMAINS_PARTNER_API_KEY!;

async function partnerApi<T>(path: string, body: unknown): Promise<T> {
  const response = await fetch(`${apiBase}${path}`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });

  const json = await response.json().catch(() => ({}));
  if (!response.ok) {
    throw new Error(json.error || json.message || `Partner API failed: ${response.status}`);
  }
  return json as T;
}

Preflight route

Call this before customer payment. It tells the frontend whether checkout may continue.

export async function POST(request: Request) {
  const { label, namespace, years = 1 } = await request.json();
  const tld = String(namespace || process.env.DEFAULT_NAMESPACE).replace(".", "");

  const result = await partnerApi<PreflightResponse>(
    `/api/v1/namespaces/${tld}/preflight`,
    { label, years }
  );

  return Response.json({
    domain: result.domain,
    available: result.available,
    canCheckout: result.available && result.can_provision,
    wholesaleUsd: result.wholesale_price_usd,
    wholesaleGems: result.wholesale_price_gems,
  });
}

Payment-success callback

Run registration only after the reseller's payment system says the customer paid. Use the payment/order id as the idempotency key.

export async function POST(request: Request) {
  const event = await request.json();

  // Replace this with Stripe webhook verification, invoice verification,
  // Bitcoin/HNS payment confirmation, or a manual paid-order lookup.
  if (event.status !== "paid") {
    return Response.json({ ok: true, ignored: true });
  }

  const tld = String(event.namespace || process.env.DEFAULT_NAMESPACE).replace(".", "");
  const paymentId = String(event.payment_id || event.invoice_id || event.order_id);

  const result = await partnerApi<RegistrationResponse>(
    `/api/v1/namespaces/${tld}/registrations`,
    {
      label: event.label,
      years: event.years || 1,
      registrant_email: event.customer_email,
      registrant_external_id: event.customer_id,
      retail_amount: event.amount,
      retail_currency: event.currency || "USD",
      retail_payment_reference: paymentId,
      idempotency_key: paymentId,
    }
  );

  // Store result.registration_id and result.domain in the downstream app DB.
  return Response.json({ ok: true, registration: result });
}

Redirect-only homepage search

For a partner's main website, use the hosted widget. It redirects to the registrar subdomain; the registrar performs authenticated preflight server-side.

<script
  src="https://partners.headlessdomains.com/widgets/search.js"
  data-registrar-url="https://my.partnerbrand.com"
  data-tlds="defiwallet,agent"
  data-placeholder="Search a Handshake name"
  data-button-label="Search">
</script>

Agent prompt

Build a white-label Handshake registrar frontend.

Use HeadlessDomains Partners only from server-side routes.
Do not expose HEADLESSDOMAINS_PARTNER_API_KEY in browser code.
Before payment, call POST /api/v1/namespaces/{tld}/preflight.
After verified payment success, call POST /api/v1/namespaces/{tld}/registrations.
Use the partner payment id as idempotency_key.
The partner is merchant of record and owns customer support.
HeadlessDomains debits the partner prepaid GFA Gems balance after successful provisioning.