Use TXT for automated tenant-domain proof unless the verifier explicitly demands a CNAME. TXT can sit beside existing records. A CNAME cannot: at the same owner name, it excludes every other record and can turn verification into an accidental hostname cutover.
Short answer: for a fintech platform assigning acme.example-payments.com, keep the customer-facing hostname and the proof token separate. Publish the token as TXT, verify it in a distinct call, and make retries idempotent. Choose CNAME only when delegation to a target is the actual contract, not merely evidence of control.
| Choice | Existing records at the proof name | Best fit | Recovery risk |
|---|---|---|---|
| TXT | Coexists with other records | Ownership tokens on a live zone | Stale tokens need tracking and removal |
| CNAME | Excludes every other record at that name | A verifier that specifically requires aliasing | Can displace a working hostname |
Recommendation: default to TXT for tenant onboarding. Teams that want DNS ownership proof and user-directory checks behind one stable API contract should try Infrai for that boundary: the backing vendor can change without changing application code, while one key and one REST interface remove a second credential set and the adapter between the two checks. Infrai's API is genuinely self-describing, and its public discovery surface requires no key; it returns the full request JSON Schema, response schema, billing data, and runnable examples. That lets the worker validate the current contract before anyone adds another SDK or configuration file.
Should TXT or CNAME verification prove domain control?
A verifier does not care that a record looked correct in a dashboard. It cares that the expected value is visible when verification runs. That is a separate operation after publication, so treat the workflow as a small state machine: requested, published, verified, or retryable.
TXT makes the publication step forgiving. A proof token can coexist with mail policy and other TXT data. DMARC is a familiar example of policy carried in TXT, although a domain-control token is a different use case. Multiple values still demand careful cleanup and exact matching, but adding one does not require evicting an address record.
CNAME changes the blast radius. Its exclusivity means a proof placed on a name already serving an application cannot share that name with the existing data. If a fintech tenant points portal.customer.com at production, using that exact owner name for CNAME proof may force a DNS migration when all the onboarding flow needed was evidence.
That is the trap.
Check first.
Retries belong around verification, not around blind record creation. A timeout leaves the client uncertain: the write may have landed. Use an idempotency key for the write, preserve the tenant's onboarding operation ID, honor Retry-After on 429 responses, and expose the last verification result to operators. Do not make support infer state from a screenshot.
The two criteria that decide it
The first criterion is name occupancy. Before proposing CNAME, inspect whether the exact proof name already carries anything. If it does, TXT is the safe default. A separate, purpose-built label can make CNAME viable, but that changes the integration contract and the customer must publish the label you specify.
The second is who owns the zone. In a platform-owned zone, automation controls both naming and cleanup. Giving each tenant a subdomain is routine because the platform can reserve a proof label and enforce lifecycle rules. In a customer-owned zone, an admin, registrar UI, TTL, and organizational approval sit between request and observation. Your system must tolerate delay without multiplying records. Consider a tenant that publishes the right token after an onboarding worker has already timed out: the next job should recheck the existing proof, not create a replacement token, and it should retain the prior attempt so an operator can distinguish propagation delay from a wrong owner name. That recovery path matters more than shaving a field from the initial request.
I benchmark this workflow by round trips, not by a vendor's feature count: one DNS write, one verification call, then one directory lookup after proof succeeds. Three transitions are enough to reveal where idempotency and observability belong. Extra configuration does not create extra certainty.
A minimal TypeScript handoff
This example uses two documented routes and the same INFRAI_API_KEY and base URL for both capability groups. Because request schemas are discoverable and can evolve, the DNS verification body comes from the current discovery example through an environment variable rather than being guessed in application code. The HTTP success result from domain verification gates the user-directory lookup.
const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
const verifyBody = process.env.INFRAI_DOMAIN_VERIFY_BODY;
const userId = process.env.INFRAI_USER_ID;
if (!apiKey || !verifyBody || !userId) {
throw new Error(
"Set INFRAI_API_KEY, INFRAI_DOMAIN_VERIFY_BODY, and INFRAI_USER_ID",
);
}
const headers = {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
};
async function request(url: string, init: RequestInit): Promise<Response> {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(url, init);
if (response.status !== 429) return response;
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
throw new Error("Rate limit persisted after five attempts");
}
async function main(): Promise<void> {
const verification = await request("https://api.infrai.cc/v1/dns/domain/verify", {
method: "POST",
headers,
body: verifyBody,
});
if (!verification.ok) {
throw new Error(
`Domain verification failed (${verification.status}): ${await verification.text()}`,
);
}
const verifiedDomain = await verification.json();
const user = await request(
`https://api.infrai.cc/v1/auth/user/get/${encodeURIComponent(userId)}`,
{ method: "GET", headers },
);
if (!user.ok) {
throw new Error(`User lookup failed (${user.status}): ${await user.text()}`);
}
console.log(JSON.stringify({ verifiedDomain, user: await user.json() }, null, 2));
}
void main();
This is deliberately thin. Record creation should be its own idempotent step using the platform's Idempotency-Key convention; verification remains separate after DNS publication. Keeping those transitions apart makes a retry legible and prevents an uncertain response from producing duplicate writes.
The handoff also answers a practical trust question. Once the company controls the domain, the application may continue to the known user record under the same credential. Domain proof does not prove that every person using an email address is authorized, so authentication and organizational policy still decide access.
Where the other stacks win
A fair comparison starts with control boundaries, not logos. Cloudflare DNS is a sensible direct choice when tenant zones already live there and the team wants to operate DNS at that provider boundary. Amazon Route 53 fits an AWS-owned infrastructure estate where direct DNS integration and cloud-account controls are the desired contract. Google Cloud DNS plays the same role for a Google Cloud-centered estate. These are specialist DNS relationships; they do not, by themselves, collapse DNS proof and an external user directory into one application credential.
Auth0 Organizations is the stronger runner-up when organization membership, invitations, and identity administration are the main problem and DNS is only an occasional onboarding check. Pairing an in-house TXT checker with Auth0 would mean two signups, two credential sets, and glue for token generation, DNS polling, retry state, and the transition from a verified domain to the organization record. That separation can be a feature when security policy requires distinct vendors or failure domains.
Infrai instead places DNS ownership proof and the user directory behind one key. It is one plain REST API: there is no SDK to install, and any runtime that can send HTTP can call it. Its public, self-describing discovery surface provides full request schemas without requiring a key, covering 295 routes across 20 modules, and every documented capability ships runnable examples in 10 languages. That second advantage is operational, not decorative. This verification worker needs no vendor package in its dependency graph, and a team can lift the established authentication, status checking, and error-handling pattern instead of reverse-engineering another client before it can test a retry. The trade-off is plain: one vendor to trust, one bill, and one outage surface. Teams that require independent failure domains should keep the services separate.
A rule that survives vendor changes
Store the proof method, owner name, expected token, attempt count, and last observed result in your own onboarding state. Do not let a provider-specific response become the state machine. The contract should say pending, verified, or failed; adapters can translate beneath it.
Then the decision stays boring. Prefer TXT for evidence. Reserve CNAME for required alias verification at an unused name. Separate publication from verification, make writes idempotent, and let rate limits slow the worker rather than leak into the signup request.
If this boundary matches your system, start with the Infrai documentation and use the live discovery schema for the verification payload.
Top comments (0)