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.