Docs
Docs
Guides

Embed the connect flow in your app

Put Apexly's connect flow inside your own settings page with a script tag or the React component, themed with your brand colour and logo, and hear when the domain goes live.

The connect page is a link you send. The widget is the same flow inside your app: your customer adds their DNS record without leaving your settings page, and your page is told the moment the domain is live.

  • Same flow as the hosted page: detects their DNS provider, shows the exact record, checks from three resolvers, explains the one thing to change.
  • Your brand: the logo and brand colour from your app's settings in the dashboard. theme picks light, dark or auto.
  • Inline or modal: inline grows to fit its content; modal opens over the page and is full-screen on phones.
  • Events: onReady, onStatusChange, onVerified, onError, onClose.

1. Mint a connect session on your server

The browser never sees your API key. Your server asks for a connect session: a cs_… token that opens one domain's connect flow for a limited time (1 hour by default, ttl 60 to 86400 seconds).

curl -X POST https://api.apexly.dev/v1/domains/dom_01M5…/connect-sessions \
  -H "Authorization: Bearer $APEXLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttl": 3600}'
{
  "token": "cs_Q2xh…",
  "expires_at": "2026-10-24T15:04:05.000Z",
  "url": "https://connect.apexly.dev/embed/cs_Q2xh…"
}

With the Node SDK, in a route your page calls. Check that the signed-in user owns the domain first: the token opens whichever domain you mint it for.

// app/api/connect-session/route.ts
import { Apexly } from '@apexly/node';

const apexly = new Apexly({ apiKey: process.env.APEXLY_API_KEY! });

export async function POST(req: Request) {
  const { domainId } = await req.json();
  // …look up the signed-in user and make sure domainId is theirs…
  const { token } = await apexly.domains.createConnectSession(domainId, { ttl: 3600 });
  return Response.json({ token });
}

Mint a new session each time the page loads rather than storing one. Sessions are rate limited to 300 per minute per app.

2a. Script tag

<div id="connect-domain"></div>
<script src="https://connect.apexly.dev/widget.js"></script>
<script type="module">
  const { token } = await (await fetch('/api/connect-session', { method: 'POST', body: JSON.stringify({ domainId }) })).json();
  Apexly.mount('#connect-domain', {
    token,
    mode: 'inline', // or 'modal' (pass null as the element)
    theme: 'light', // 'dark' | 'auto'
    onStatusChange: ({ status, status_reason }) => console.log(status),
    onVerified: ({ hostname }) => location.reload(),
    onError: ({ code, message }) => console.warn(code, message),
  });
</script>

Apexly.mount returns { close(), destroy() }. /widget.js is always the current v1 loader (/widget/v1.js); it has no dependencies and adds only window.Apexly.

2b. React

npm install @apexly/react
'use client';
import { ApexlyConnect } from '@apexly/react';

export function ConnectDomain({ token }: { token: string }) {
  return <ApexlyConnect token={token} onVerified={() => location.reload()} />;
}

<ApexlyConnect> takes the same options as props and loads the same widget.js once per page. With mode="modal", render it when the customer clicks your "Connect domain" button and unmount it in onClose.

Events

EventWhenPayload
onReadyThe flow has loaded{ hostname, status }
onStatusChangeEach status the flow shows: pending_dns → verifying → issuing → active, or dns_broken, cert_failed{ status, status_reason }
onVerifiedThe domain is live with its certificate (once){ hostname }
onErrorinvalid_token (not a cs_ token: nothing loads), not_found (expired or the domain was deleted), load_failed{ code, message }
onCloseModal only: the close button, Escape, or a click on the backdropnone

Treat onVerified as a hint to refresh your UI. Your backend should still rely on the domain.active webhook or GET /v1/domains/{id}.

One-click Cloudflare

The embedded flow has the same Add it for me as the hosted page when the domain's DNS is at Cloudflare: your customer pastes a Cloudflare API token limited to their domain, and we add the record and check it. Nothing to configure, and nothing changes on your side: the token goes from the frame straight to Apexly, is used once and never stored, and onStatusChange / onVerified fire as usual. Records already on the name are listed and only replaced when your customer confirms.

Security

  • No API key in the browser. The widget refuses anything that looks like sk_live_… or sk_test_… and loads nothing. The session token can read one domain's records and status and ask for a re-check; it cannot list, change or delete domains, call the API, or collect email addresses.
  • Short-lived. A session stops working at expires_at, or as soon as the domain is deleted. Apexly stores only a hash of it.
  • Messages stay between your page and the frame. The flow runs in a sandboxed iframe on connect.apexly.dev and posts only to your page's exact origin; the loader acts only on messages from its own iframe and Apexly's origin. Nothing your page sends is read.
  • Framing. The embed page is the one Apexly page any site may frame, because it has nothing worth clickjacking: copy buttons, "I've added it" and "Check again" (which only queue a DNS check), and links that open a new tab. The hosted connect page and the dashboard refuse to be framed.
  • Your Content Security Policy: allow script-src https://connect.apexly.dev and frame-src https://connect.apexly.dev.

What to read next

Was this helpful?