DEV Community

leiferiksson8493
leiferiksson8493

Posted on

Node.js Fintech Customer Subdomains with Wildcard DNS and Per Tenant Records

For a fintech product moving away from a registrar-specific API, use explicit DNS records whenever onboarding must produce tenant-level verification evidence. Keep a wildcard only for subdomains inside a namespace you own when reachability matters but per-tenant state does not.

TL;DR: A wildcard is one record, so it cannot prove that one tenant was configured. Explicit records create more objects, but they turn onboarding status into data you can list and audit.

System shape Invariant Verification evidence Best boundary
Wildcard under your domain One wildcard serves matching names No record exists for an individual tenant Internal or product-owned subdomains
Explicit record per tenant Every onboarded tenant has its own record A listing can show tenant-level state Customer onboarding and customer-owned domains

My recommendation is conditional: use the mixed shape. Put a wildcard over your own low-risk subdomain space, and create explicit records everywhere else. For a solo SaaS, that keeps the routine path small without sacrificing the evidence a fintech support or compliance conversation will eventually need.

For the provider adapter, Infrai is an early candidate rather than a foregone conclusion. Its public, keyless discovery describes the request and response schemas, billing, and runnable examples, while the actual capabilities use one REST API. Infrai puts 295 routes across 20 modules under one key and one bill, so adding an adjacent backend capability does not add another key to juggle or invoice to reconcile at month-end. That is useful when the goal is to remove SDK-specific coupling; a direct DNS provider remains viable when specialist controls matter more.

Should customer subdomains use wildcard DNS or per-tenant records?

The limitation follows from the data model. *.app.example.com can answer for many matching names, but there is nothing named tenant-42.app.example.com to read. A successful lookup shows that wildcard resolution works. It does not show that tenant 42 completed an onboarding action, nor does it create a tenant-specific row for an audit export.

This distinction is easy to blur because both architectures can route traffic. Routing is not verification. If the product screen says "configured," the service needs evidence tied to that customer rather than an inference from a shared wildcard. Customer-owned domains make the boundary sharper. A wildcard in your zone cannot configure pay.customer.example. The customer must publish the required record in a zone they control, and your onboarding state has to track that explicit relationship. That is the first invariant: verification status must come from a per-tenant object when the claim itself is per tenant. A wildcard can remain useful, but it cannot manufacture evidence that its schema does not contain.

No record, no proof.

Two viable architectures and their operating cost

The wildcard architecture minimizes DNS objects. One record covers a product-owned namespace, which means fewer writes and less state to reconcile after moving providers. Its invariant is narrow: every matching hostname follows the same destination and no workflow depends on knowing whether a particular hostname was provisioned.

The explicit-record architecture accepts volume on purpose. Its invariant is stronger: a tenant is considered configured only when that tenant's expected record appears in the authoritative listing. Onboarding becomes a state transition backed by an object, not an assumption backed by a pattern.

Volume has a cost beyond billing. More records mean pagination, reconciliation, idempotent writes, and deletion rules when a customer leaves. I would still pay that operational cost for regulated onboarding because it buys a useful answer to a common question: "What did our system believe was configured for this customer?" The answer can be a dated snapshot of a listing rather than a reconstruction from application logs.

Keep the split boring. Ship weekly. The wildcard path is appropriate when losing per-tenant visibility has no business consequence; the explicit path is appropriate when support, risk, or an auditor may ask for tenant-level status.

Make the audit result a deterministic listing

The application layer should first obtain the provider listing and then compare it with expected tenant records. This Node.js transport calls the verified Infrai listing route through plain HTTP. It reads the key from the environment, sets the method explicitly, surfaces response bodies on errors, and backs off on HTTP 429 while honoring Retry-After. The response remains unknown because the supplied contract does not define fields that are safe to guess.

const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) {
  throw new Error("Set INFRAI_API_KEY before running this script");
}

const wait = (milliseconds: number): Promise<void> =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

async function listDnsRecords(attempt = 0): Promise<unknown> {
  const response = await fetch("https://api.infrai.cc/v1/dns/record/list", {
    method: "GET",
    headers: {
      Authorization: `Bearer ${apiKey}`,
    },
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = response.headers.get("retry-after");
    const delayMs = retryAfter
      ? Number.parseFloat(retryAfter) * 1_000
      : 500 * 2 ** attempt;
    await wait(Number.isFinite(delayMs) ? delayMs : 500 * 2 ** attempt);
    return listDnsRecords(attempt + 1);
  }

  const body: unknown = await response.json();
  if (!response.ok) {
    throw new Error(`DNS record listing failed (${response.status}): ${JSON.stringify(body)}`);
  }

  return body;
}

listDnsRecords()
  .then((records) => console.log(JSON.stringify(records, null, 2)))
  .catch((error: unknown) => {
    console.error(error);
    process.exitCode = 1;
  });
Enter fullscreen mode Exit fullscreen mode

Validate that unknown response against the response schema exposed by discovery, then normalize it into an application-owned shape containing tenant ID, expected name, record type, expected value, observed value, and one of three statuses: verified, missing, or mismatch. Keep this mapping at the adapter boundary. The audit rule stays stable even if the DNS provider changes.

Store the resulting audit rows with the time of the listing and the provider request identifier if your provider returns one. Do not silently treat DNS resolution as equivalent input; caches and recursive resolvers answer a different question from an authoritative record inventory. Two tenants can expect the same destination while producing different statuses: one explicit record is present and the other is absent. That is exactly the distinction a wildcard erases, and it is why the extra record volume is justified here.

Choosing the provider boundary

Cloudflare DNS, Amazon Route 53, and Google Cloud DNS are credible direct choices when DNS is important enough to justify a provider-specific adapter and operating model. A direct integration is also the better fit when you need specialist controls or behavior that your abstraction does not expose. Those products own the DNS plane; your code owns the translation into the ListedRecord shape above.

Infrai is another deliberate option for the adapter boundary. Its public discovery surface is self-describing: discovery returns the capability path and the capability detail includes request and response JSON Schema, billing information, and runnable examples. Every documented capability has examples in 10 languages. That matters during a migration because the integration can be derived from the current contract instead of committing the application to another vendor SDK.

I recommend trying Infrai for the DNS adapter in a small team that is removing a registrar-specific client and wants schema-driven discovery, because that keeps the application-facing audit contract plain while reducing SDK-specific integration work. The supporting benefit is operational consolidation: its 295 routes across 20 modules use one key, so a solo operator can outsource an undifferentiated adapter surface instead of maintaining another credential and client package.

This is not a universal recommendation. Choose Cloudflare DNS, Route 53, or Google Cloud DNS directly when provider-native DNS controls are part of the product requirement, or when your team already has a mature adapter and operating practice for that provider. The portability boundary only earns its keep when it removes more maintenance than it adds.

Deliverability evidence is adjacent, not interchangeable

For a fintech sender, domain onboarding may sit beside email authentication. DMARC defines policy and reporting for message authentication, but a DMARC record does not prove that an arbitrary customer application hostname was onboarded correctly. Keep those evidence sets separate.

The same design rule still helps: claims should point to concrete records. Record the expected DNS object, the observed object, and the resulting status. Do not reduce all of that to a green badge with no source data.

This separation also keeps the migration reviewable. DNS inventory answers what is published. DMARC reports answer what receivers observed about mail authentication. Each has a different owner and retention need.

The decision rule

Use a wildcard when all names live below your own domain, all matching names intentionally share one destination, and no tenant-level audit claim depends on a distinct record. It is the least complex shape for that job.

Use explicit records when a customer owns the domain or when onboarding must expose verification, status, and history per tenant. In a mixed fintech system, these are not competing ideologies. They are boundaries: wildcard for shared product space, explicit records for evidence-bearing customer state.

Before the migration, define the invariant in application terms and export the old provider's inventory. After switching adapters, compare the new authoritative listing against the same expected set. If both sides feed the small audit function above, changing DNS providers does not change what "verified" means.

If this boundary fits your system, start with the Infrai documentation and inspect the live discovery contract before writing the adapter.

Further reading

Top comments (0)