Widget
The widget is a three-step Web Component for hostname entry, DNS instructions and live HTTPS status. Your API key remains on your server; the browser receives a short-lived token bound to its origin and the domains it creates.
The preview uses local demo data and makes no API request.
1. Create a session on your server
POST /widget-sessions · scope widget:write · idempotent · response 201
curl -X POST https://api.subdomain.to/v1/widget-sessions \
-H "Authorization: Bearer $SUBDOMAINTO_API_KEY" \
-H "Idempotency-Key: widget-customer-42" \
-H "Content-Type: application/json" \
-d '{"external_customer_id":"customer-42","allowed_origins":["https://app.example.com"]}'
const session = await client.widget.createSession('widget-customer-42', {
externalCustomerId: 'customer-42',
allowedOrigins: ['https://app.example.com'],
});
$session = $client->widget()->createSession(
'widget-customer-42',
'customer-42',
['https://app.example.com'],
);
session = client.widget.create_session(
"widget-customer-42",
external_customer_id="customer-42",
allowed_origins=["https://app.example.com"],
)
The response contains token, expires_at, and expires_in: 1800. Send only token to the browser. Allowed origins must be HTTPS origins without a path, query or fragment; HTTP is accepted only for localhost development.
2. Embed the component
<script type="module" src="https://cdn.subdomain.to/widget/v1/subdomain-widget.js"></script>
<subdomain-to-widget
token="WIDGET_SESSION_TOKEN"
api-base="https://api.subdomain.to"
lang="en">
</subdomain-to-widget>
| Attribute | Default | Purpose |
|---|---|---|
token |
required | Short-lived token returned by /widget-sessions. |
api-base |
https://api.subdomain.to |
API origin, mainly overridden in local development. |
lang |
en |
UI language: en or fr. |
demo |
absent | Uses deterministic local data and never calls the API. |
3. Listen for lifecycle events
const widget = document.querySelector('subdomain-to-widget');
widget.addEventListener('subdomain-to:created', ({detail}) => {
console.log('Created domain', detail.id);
});
widget.addEventListener('subdomain-to:status', ({detail}) => {
console.log('Current status', detail.status);
});
widget.addEventListener('subdomain-to:error', ({detail}) => {
console.error(detail.code, detail.message);
});
subdomain-to:created is emitted after creation. subdomain-to:status is emitted after creation and whenever the lifecycle status changes. subdomain-to:error contains a stable code and safe message.
Browser endpoints and polling
The component calls these endpoints with the widget token as a Bearer token:
| Endpoint | Purpose |
|---|---|
POST /widget/domains |
Create the domain and return dns_records. |
GET /widget/domains/{id} |
Read the domain status every 10 seconds. |
POST /widget/domains/{id}/verify |
Request an immediate DNS verification. |
The API enforces the browser Origin, organization and widget session. A session cannot read or verify a domain created by another session. Polling stops when the domain becomes active or terminal, the token expires, or the component is removed.
Theme
subdomain-to-widget {
--sdt-primary: #635bff;
--sdt-background: #ffffff;
--sdt-color: #111827;
--sdt-border-radius: 14px;
}
The component uses Shadow DOM, so these custom properties are the supported styling interface.
Errors
Common error codes include missing_token, widget_token_expired, invalid_widget_token, origin_forbidden, destination_not_found, domain_conflict, widget_domain_not_found, and widget_request_failed. When a token expires, create a new session on your server and replace the component's token attribute.