Give each fintech tenant a platform-owned subdomain first, and activate it from the platform's own DNS change. For a customer-owned hostname, run scheduled verification in the background and offer a customer-triggered recheck as an extra control. A button alone cannot establish that a DNS change has reached the resolver doing the check.
Short answer: use the DNS ownership boundary to choose the default, then treat a manual recheck as a fast feedback path rather than a second source of truth.
| Hostname arrangement | Verification clock | What the tenant sees |
|---|---|---|
acme.payments.example under a platform-owned zone |
Platform deployment and internal provisioning status | Ready after the platform confirms its own configuration |
pay.acme.example under a customer-owned zone |
Scheduled checks, plus a rate-limited recheck | Pending, last check time, and the exact record to publish |
The recommendation is deliberately asymmetric. If the platform controls the zone, asking the customer to refresh verification adds a dependency without adding evidence. If the customer controls it, the platform cannot tell when the record was published; background checks make progress possible even when nobody keeps the onboarding tab open.
Who can actually change the record?
Control is the first criterion. A platform-owned subdomain belongs in the tenant provisioning workflow: reserve a unique label, create the intended DNS configuration, and associate the hostname with the correct tenant before exposing it. Avoid declaring it ready merely because a write request was accepted. The serving configuration must also be ready. In a financial application, mapping a hostname to the wrong tenant is a more serious failure than a delayed activation.
Customer-owned zones need a different handshake. Give the customer a specific owner name, record type, and unique verification value, then check what DNS returns for that owner name. Keep the challenge bound to the tenant and hostname; seeing any TXT record at the name is not proof of control. A successful check proves that the requested value was visible to the verifier. It does not, by itself, prove that routing or certificate issuance has finished. A tenant might paste the record at the zone apex instead of the requested _verify name; the correct token at the wrong owner name still fails the check, so show the expected name beside the last observed outcome.
That distinction keeps the UI honest. Show separate states such as awaiting DNS, verified, and ready to serve. Store the last observed result and check time. Never turn a failed lookup into a permanent rejection when the customer may still be editing the zone.
Ownership comes first.
Should customer-triggered rechecks replace scheduled domain verification polling?
Feedback latency is the second criterion. DNS responses carry TTLs, and negative answers can be cached too. A customer may have published the right record while a resolver still returns an earlier absence. Repeated clicks against the same cached path do not force authoritative servers or intermediate caches to change their answers. This is why a recheck should report what this verifier observed, not promise global propagation.
Use a scheduled retry so verification continues after the customer closes the page. Space retries out, bound the verification window, and keep pending work visible for support. The manual action can enqueue an earlier check, subject to a per-hostname cooldown and the same verification rules. It should not bypass token matching or flip a status flag directly.
Not always.
Fast feedback is still valuable. A tenant operator who fixes a typo should not have to wait for the next scheduled pass just to learn whether the verifier can now see the record. But spending engineering hours on aggressive polling is a poor revenue-per-hour trade when DNS caching sets a floor on useful feedback. Ship a clear status view first; refine retry intervals from observed pending times and query volume, not from the hope that more requests will make DNS faster.
What does the verification worker need to remember?
A small state machine is enough. Persist the requested hostname, tenant ID, challenge, attempt count, next check time, and last observation. A worker claims due records; a customer recheck moves an eligible record forward in the queue. Both routes call the same verifier. This avoids two implementations disagreeing about what counts as valid.
type Check =
| { kind: "verified"; observedAt: Date }
| { kind: "pending"; observedAt: Date; reason: "missing" | "mismatch" | "lookup-error" };
async function verifyTxt(
hostname: string,
expected: string,
lookupTxt: (name: string) => Promise<string[][]>,
now: () => Date,
): Promise<Check> {
const observedAt = now();
try {
const records = await lookupTxt(`_verify.${hostname}`);
if (records.some((chunks) => chunks.join("") === expected)) {
return { kind: "verified", observedAt };
}
return { kind: "pending", observedAt, reason: records.length ? "mismatch" : "missing" };
} catch {
return { kind: "pending", observedAt, reason: "lookup-error" };
}
}
TXT character strings can be split into multiple chunks in one resource record, so the comparison joins chunks within each record, not across records. The lookup function here is an injected resolver interface, not a promise that every lookup error means the record is absent. Production handling should retain the error category for operations and distinguish transient resolver failures from a visible mismatch. Do not display the secret challenge in public logs or use a shared challenge across tenants.
Test this with a fake resolver returning missing records, a stale answer, a wrong token, a matching token, and a transient error. Then test the race: a scheduled job and a button press may run together. Make the state update idempotent and require the active challenge to match before marking the hostname verified. A later hostname reassignment must not inherit an old success.
Where does the manual-first approach win?
For a small set of customer-owned domains with an operator present throughout setup, a recheck button can be the primary interaction. It gives immediate feedback after an edit and reduces background lookup traffic. The limitation is operational: abandoned onboarding stays pending until someone returns, and a support agent may need to prompt a retry. That can be acceptable when domain setup is explicitly a supervised process. Conversely, scheduled polling is a poor fit for a deliberately supervised, rarely used custom-domain feature when DNS queries, job scheduling, and alerting would add more maintenance than the setup volume warrants. Pick manual-first in that case; do not sell background work as free reliability.
For automatic tenant subdomains, that is the wrong default. Customers should not need to understand DNS to receive a platform-owned address. For self-serve custom hostnames, the scheduled worker earns its place because setup can span browser sessions and DNS cache lifetimes. Keep the button, but let the job finish. Outsource undifferentiated DNS hosting or resolver operations where appropriate; keep tenant binding, challenge validation, and activation policy under your application's control. A weekly shipping cadence leaves little room for hand-operated verification queues.
Measure time from record instructions shown to first verified observation, checks per pending hostname, lookup errors, and time from verification to serving readiness. Separate those durations. Otherwise a routing delay gets mislabeled as DNS propagation, and the team optimizes the wrong stage.
Top comments (0)