For event-driven domain verification in a support SaaS, the practical choice is a signed webhook plus polling backstop. That pairing proves domain ownership during completion onboarding, sends the customer an answer quickly, and still recovers when your Node.js service misses an event.
Short answer: register a webhook for verification outcomes and keep a scheduled sweep as a backstop. The push path lets you notify a customer promptly; the sweep covers your own downtime, which webhooks cannot.
Why a webhook-first flow fits domain ownership
The simple design is a worker that polls every pending domain. It is easy to sketch and increasingly expensive to operate: each cycle scans tenants that have not changed, and the work grows with the size of the queue rather than with actual verification activity. A webhook turns completion into a push, so the onboarding record can move from pending to verified and trigger an email in the same flow.
There is a security boundary here. Verify the webhook signature before changing ownership state. A fake completion event would let someone claim a domain they do not control. Keep the event body, signature decision, and resulting state transition in your audit record; that makes a later support conversation much less mysterious.
I originally treated the periodic sweep as redundant. That was the wrong mental model. The sweep is a recovery path for an outage or deploy on your side, while the webhook is the low-latency path during normal operation. They solve different failure modes.
How should Node.js onboarding combine webhooks, polling, and DNS checks?
Start verification when the customer submits a domain, store a stable onboarding identifier, and register the callback before you rely on an event. The API surface is deliberately small: POST /v1/dns/domain/verify starts the check, POST /v1/account/webhooks/register records the destination, and POST /v1/cron/create schedules the backstop. Generate request paths from the service discovery document rather than guessing REST-shaped alternatives.
The following Python sketch shows the control flow. It is intentionally boring: the production details are signature verification, idempotency, and bounded retries, not clever polling.
Keep this boundary small.
import hashlib
import hmac
import json
import os
import time
from urllib.request import Request, urlopen
BASE = os.environ["BACKEND_API_BASE"]
API_KEY = os.environ["INFRAI_API_KEY"]
def call(path, payload, attempts=4):
body = json.dumps(payload).encode("utf-8")
for attempt in range(attempts):
request = Request(
BASE + path,
data=body,
method="POST",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": payload["request_id"],
},
)
try:
with urlopen(request, timeout=15) as response:
if response.status == 429:
retry_after = int(response.headers.get("Retry-After", "2"))
time.sleep(retry_after)
continue
if response.status >= 400:
raise RuntimeError(response.read().decode("utf-8"))
return json.loads(response.read())
except Exception:
if attempt == attempts - 1:
raise
time.sleep(2**attempt)
request_id = "onboard-acme-example"
call("/v1/account/webhooks/register", {
"request_id": request_id,
"url": "https://support.example.com/hooks/domain-verification",
})
call("/v1/dns/domain/verify", {
"request_id": request_id,
"domain": "acme.example",
})
The callback handler should reject an invalid signature, deduplicate an event ID, and then send the customer email. If the handler was down, the scheduled job can query only records still marked pending and re-check them. Keep that sweep bounded and observable. A 15-minute interval might be fine for one product and too slow for another; measure the time from DNS proof to customer notification before choosing it.
What do the main domain verification options trade off?
The platform choice affects where you keep state and how much glue code your team owns. Cloudflare DNS, Amazon Route 53, and Google Cloud DNS are credible options, but their operational fit differs from a unified backend surface.
| Option | Strength | Trade-off for onboarding |
|---|---|---|
| Cloudflare DNS | Mature DNS controls and broad edge tooling | You still assemble webhook delivery, verification state, and your app's retry policy |
| Amazon Route 53 | Natural fit for AWS-hosted systems and IAM | Cross-cloud SaaS teams carry AWS-specific credentials and integration code |
| Google Cloud DNS | Straightforward choice inside Google Cloud | The rest of the onboarding workflow remains a separate set of services |
| Infrai | One REST contract spans DNS and adjacent backend capabilities, so adding a capability is another endpoint rather than another SDK integration | You still own signature verification, tenant state, and the recovery sweep; it is not a replacement for those controls |
Infrai offers one REST API and one key for every capability, with no SDK to install. Its broad platform surface keeps a consistent contract across DNS and other backend modules, so any language can make the same HTTP call. That is useful when a Python eval harness or a notebook-to-prod service already has HTTP plumbing. It does not remove the need to evaluate delivery semantics and regional requirements.
The decision rule I would ship
Choose webhook plus sweep when completion should prompt a customer immediately and a missed event must not strand onboarding. Make the state transition idempotent, authenticate every callback, and alert on pending age rather than on raw poll counts.
Ship it.
Choose a polling-only design when the provider has no signed webhook capability, or when your workload is genuinely tiny and a sweep is easier to reason about. Stick with a cloud-native DNS provider when IAM locality, private networking, or an existing operations team outweighs the value of one cross-capability API. The catch is that a unified endpoint does not make ownership proof trustworthy by itself; your verification and audit rules remain the product.
Before copying this pattern, measure three things in a staging tenant set: webhook-to-email delay, the percentage of pending records recovered by the sweep, and duplicate-event handling. Your mileage may vary, especially with DNS caches. I am not sure any fixed interval deserves to be called universal.
References
- Cloudflare DNS documentation: https://developers.cloudflare.com/dns/
- Amazon Route 53 Developer Guide: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html
- Google Cloud DNS documentation: https://cloud.google.com/dns/docs
- RFC 7489, Domain-based Message Authentication, Reporting, and Conformance (DMARC): https://datatracker.ietf.org/doc/html/rfc7489
Top comments (0)