Use a DNS TXT challenge to prove a fintech customer controls a domain, then use a service registry for internal cutovers. This split keeps the audit evidence tied to a public standard while avoiding DNS cache delays during frequent deploys.
| Situation | Primary mechanism | Pick it when | Main risk |
|---|---|---|---|
| Prove a customer domain before onboarding | DNS TXT record | An external resolver must verify control | TTL and negative caching delay a retry |
| Route internal calls during deploys | Service registry with health state | Instances change several times a day | Stale or inconsistent registry data |
| Keep a stable name for low-churn services | DNS A/AAAA or CNAME | The target changes rarely | Cutover waits on cached answers |
| Need both auditability and fast rollback | DNS proof plus registry lookup | Onboarding and runtime have different clocks | Two sources of truth unless ownership is explicit |
The practical rule is short: DNS answers the question “does this organization control the name?” A registry answers “which healthy instance should receive this request right now?” Treating either system as the other creates confusing incidents.
Should internal service discovery use DNS records or a registry?
A fintech onboarding flow usually has a customer add a domain, publish a token, and wait for verification. The verifier asks recursive DNS resolvers for a TXT record. Those resolvers cache positive answers for the record's TTL, and they can cache a missing answer under the zone's negative-caching rules. A record that was just added can therefore be invisible to the verifier for a while, even when the authoritative server is already correct.
Runtime traffic has a different tolerance. During a deploy, an unhealthy instance should disappear from selection quickly. A registry can store health and lease state close to the caller, so a client can refresh in seconds without asking the whole DNS hierarchy to forget an old answer. That speed comes with an operational obligation: define what happens when the registry is stale, partitioned, or unavailable.
I start design reviews by writing those clocks down. For example, an onboarding verifier may poll at 30-second intervals, while a payment-adapter deploy can require a sub-minute rollback. The numbers are policy choices, not properties of DNS. Naming them makes the trade-off visible to security, SRE, and compliance teams.
Field guide: choose by failure mode
Pick DNS proof when the verifier must observe control from outside your network. A random, single-use token in a TXT record is easy to log and revoke. Keep the token scoped to the tenant and bind it to an onboarding attempt; accepting a permanent, guessable value turns a one-time check into a standing credential.
Pick a registry for an internal name whose endpoints churn. Store an instance identifier, address, port, protocol, and expiry. Health checks should update eligibility, while a lease timeout removes an instance that stopped reporting. Clients need a bounded cache so a registry outage does not become a request storm.
Pick DNS service discovery for stable internal names and coarse-grained changes. A low TTL can reduce waiting, but every recursive lookup still has network and resolver behavior to account for. DNS also has no built-in notion of application health; publishing an address does not prove that the process can serve a payment request.
The trade-off is explicit: propagation delay belongs on the onboarding path, while cutover speed belongs on the runtime path. The useful hybrid is explicit ownership: DNS is authoritative for the customer-to-tenant proof, while the registry is authoritative for live backend membership. A control-plane record can link the tenant identifier to the registry namespace without putting volatile instance data into public DNS.
A small implementation with observable state
The verifier below separates “record observed” from “onboarding accepted.” That distinction matters when a resolver returns an older, valid value. Every attempt gets a correlation ID, and the result records which resolver view was seen.
type VerificationResult = {
status: "verified" | "pending" | "rejected";
observedToken?: string;
resolver: string;
checkedAt: string;
};
async function verifyDomain(
domain: string,
expectedToken: string,
resolveTxt: (name: string) => Promise<{ values: string[]; resolver: string }>
): Promise<VerificationResult> {
const name = `_onboarding.${domain}`;
const { values, resolver } = await resolveTxt(name);
const observedToken = values.find((value) => value.startsWith("token="));
const checkedAt = new Date().toISOString();
if (!observedToken) {
return { status: "pending", resolver, checkedAt };
}
if (observedToken !== `token=${expectedToken}`) {
return { status: "rejected", observedToken, resolver, checkedAt };
}
return { status: "verified", observedToken, resolver, checkedAt };
}
Emit a metric for each state transition: domain_verification_pending, domain_verification_verified, and domain_verification_rejected. Include labels for resolver class and region, but avoid putting the full customer domain in high-cardinality labels. Log the correlation ID, tenant ID, authoritative lookup timestamp, and observed TTL. Those fields let an operator tell “record never published” from “record published but cached.”
For the runtime side, make registry freshness a first-class signal. Track the age of the last successful refresh, the count of eligible instances, and request failures after a registry update. Alert on an increasing freshness age and on a sudden drop to zero eligible instances. A green DNS lookup does not clear a registry alert.
How should deploys and rollbacks be timed?
During a deploy, register the new instance before sending traffic, wait for its health state to become ready, then drain the old instance. Keep the client cache lifetime shorter than the maximum tolerated rollback delay, with jitter so a fleet does not refresh simultaneously. If the registry is unavailable, serve from the last known set only for a bounded interval; after that, fail closed for sensitive operations rather than routing blindly.
DNS changes belong in a slower lane. Publish the proof record, observe it from more than one recursive resolver, and only then mark onboarding complete. When a customer removes a domain, retain a tombstone in your control plane so a delayed positive cache cannot be mistaken for a new authorization.
It failed.
This ordering gives you a clean incident narrative: ownership verification can be pending without blocking healthy internal traffic, and a backend rollback does not require waiting for public caches.
Limits to keep on the page
Neither mechanism is a consensus database for business state. DNS data can be stale by design, and a registry can be stale because its control plane failed. Do not encode payment authorization, tenant status, or secrets in records intended for discovery.
That is the trap.
The split also adds reconciliation work. Periodically compare the tenant-to-namespace mapping with registry membership, and expire mappings that have no active owner. Test resolver behavior in the regions where customers operate; a single successful lookup is weak evidence.
When the business cannot tolerate either stale DNS proof or stale membership, add an explicit, strongly consistent authorization check in the request path. Discovery should narrow the destination, not make the final security decision.
Top comments (0)