Webhooks
Webhooks avoid continuously polling resource state. Each delivery sends JSON to an HTTPS URL.
Create an endpoint
POST /webhook-endpoints · scope webhooks:write · idempotent · response 201
curl -X POST https://api.subdomain.to/v1/webhook-endpoints \
-H "Authorization: Bearer $SUBDOMAIN_TO_API_KEY" \
-H "Idempotency-Key: webhook-production" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/webhooks/subdomainto","events":["hostname.created","hostname.deleting"]}'
events defaults to ['*']. The secret field is returned only by this creation response; store it immediately.
Delivery format
{"id":"01J00000000000000000000004","event":"hostname.created","created_at":"2026-07-15T10:30:00+00:00","data":{"id":"01J...","hostname":"portal.customer.com"}}
Verify the signature
The header is SubdomainTo-Signature: t=timestamp,v1=signature. The signature is the HMAC-SHA256 of timestamp + "." + raw_body using the endpoint secret.
import { verifyWebhook } from '@subdomainto/node';
const event = verifyWebhook(rawBody, signatureHeader, endpointSecret);
Always use the untouched raw body, compare signatures in constant time, and reject stale timestamps. Official SDKs implement these checks with a default tolerance of 300 seconds.