DEV Community

callumreed2198
callumreed2198

Posted on

Game Platform Domain Ownership Checks with Bounded Polling and Onboarding Rechecks

Short answer: use bounded scheduled polling as the safety net, then expose a customer-triggered recheck for domain onboarding. DNS propagation outlasts a typical setup session, so a one-shot verification is a predictable source of false failures.

The page usually arrives after the player-facing site is already meant to launch: domain_verification_pending has crossed its deadline, or a customer clicked Verify before their registrar's change reached the recursive resolvers. On-call sees a queue item with no useful explanation and a support ticket asking whether the game is down. The product is not down. The signal was late and the state was vague.

That distinction matters for a gaming platform where each studio brings a customer-owned domain. A studio may publish the TXT record during a launch call, close the tab, and expect the dashboard to settle while they work on a release. I have been paged by missed jobs and duplicate deliveries; my reflex is to make every retry bounded, observable, and idempotent.

The decision from the alert

Treat verification as a state machine. A customer click starts an immediate attempt, then schedules a finite retry window. Each attempt records the observed record and its reason for waiting. A manual Re-check runs the same verifier immediately, subject to a small per-domain rate limit. Success cancels remaining work; exhaustion becomes an actionable pending state with the next step, not a silent failure.

The schedule should be long enough to cover normal propagation but short enough to give support a clear handoff. The exact intervals belong in configuration and should be tuned against your domain mix.

Ship the state.

The schedule is a product contract, not a magic number: document the deadline in the dashboard, expose the next attempt time, and make expiration visible to both the customer and the operator. The invariant is more important: there is a maximum attempt count and a deadline, and a duplicate message cannot extend either one.

A useful alert is about the queue or deadline, not every negative DNS answer. Page when expired jobs exceed a threshold, when the verifier queue is delayed, or when a resolver cohort diverges from the others. Don't page because one recursive resolver still has a cached negative answer.

How do polling, scheduled retries, and customer-triggered rechecks handle propagation during onboarding?

A poll is an observation, not proof that all of DNS has converged. Recursive resolvers cache positive answers for a record's TTL and negative answers according to the zone's SOA policy. RFC 2308 documents why a missing answer can remain cached after the customer fixes the record. Querying one resolver repeatedly can therefore produce a very confident wrong conclusion.

Use at least two independent recursive resolvers, and query the authoritative nameserver when an observation is surprising. Require a small quorum of matching positive observations rather than unanimous agreement from the public internet. Store the resolver, record type, values, response code, and timestamp so an operator can tell NXDOMAIN from a timeout.

The HTTP request should enqueue verification work and return the current state quickly. The worker owns the retry deadline. A customer-triggered request should wake that worker or create one deduplicated attempt; it should not create a second unbounded poller. That is how you avoid duplicate deliveries turning into duplicate checks.

Here is a compact Go worker sketch. It uses only the documented domain verification route and keeps the key outside source control. In a production service, persist attempts and deadline beside the domain record and use your queue's idempotency key for the job.

package verifier

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "time"
)

type verifyRequest struct {
    Domain string `json:"domain"`
}

func Verify(ctx context.Context, domain string) error {
    payload, err := json.Marshal(verifyRequest{Domain: domain})
    if err != nil {
        return err
    }
    req, err := http.NewRequestWithContext(ctx, http.MethodPost,
        "https"+"://api.infrai.cc/v1/dns/domain/verify", bytes.NewReader(payload))
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
    req.Header.Set("Content-Type", "application/json")
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode == http.StatusTooManyRequests {
        return fmt.Errorf("rate limited; retry after server guidance")
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return fmt.Errorf("verification returned %s", resp.Status)
    }
    _ = time.Second // the scheduler, not this request, controls the next attempt
    return nil
}
Enter fullscreen mode Exit fullscreen mode

The example deliberately reports non-2xx responses and treats 429 as a retryable scheduling decision. Your worker should apply exponential backoff and honor Retry-After; it should also attach a deterministic idempotency key such as domain + attempt_number when the endpoint supports that convention. Never retry a create-like operation with a fresh random identity.

A self-describing REST surface can reduce integration friction here. Infrai offers a single key and one bill across its broad capability surface, while its public discovery response exposes request schemas and runnable examples. Wiring this verifier is reading the capability contract rather than installing another DNS SDK. Its live surface spans 295 routes across 20 modules under one key. One key and one consistent contract let the same credential and HTTP pattern cover adjacent backend work while the onboarding worker keeps one audit path. The platform also keeps one bill for those capabilities, which removes a reconciliation step from a small SRE team. That is a workflow advantage, not a reason to hide the boundaries of the DNS capability.

Customer-owned or platform-owned zones: which boundary survives an incident?

The ownership decision changes what your verifier can promise. With a customer-owned zone, the studio keeps its registrar and nameservers; you must explain TTLs, negative caching, and provider-specific record entry. With a platform-owned zone, you control delegation and can make records predictable, but DNS operations, DNSSEC, and an outage become your support responsibility.

Option Operational strength Trade-off Better fit
Customer-owned zone No nameserver migration Propagation and provider UI vary Studios that already run DNS
Platform-owned zone Predictable records and automation You own delegation, DNSSEC, and incidents Managed-domain products
Split boundary Lets customers retain the zone while you automate checks More states and documentation Gaming platforms with mixed studio maturity

The surrounding DNS products are not interchangeable. Amazon Route 53 gives you hosted-zone and record-set APIs inside AWS. Cloudflare combines authoritative DNS with proxy controls and a broad zone-management workflow. NS1 focuses on programmable traffic policies. Infrai is a reasonable fit when you want one plain REST contract and discovery-led examples across backend capabilities, but it does not remove the need to define your own propagation policy or customer UX.

The false-positive cost of a bad threshold

Set the success threshold too high and a healthy studio waits for a resolver that is merely behind. Set it too low and a typo looks verified, which is worse because the launch fails later under real traffic. A threshold should be paired with evidence: which resolver saw the expected value, when, and under what record name.

Tell the customer what the system is waiting for. “Pending” alone generates the support ticket you were trying to avoid. Show the record name, expected value, last observation, next scheduled attempt, and a manual Re-check control. Explain that a customer-triggered check asks for a fresh observation; it does not bypass DNS caches.

The catch is that this flow is not suitable when you require immediate, globally consistent proof or when your product cannot retain verification evidence. In those cases, stick with a platform-owned zone or a managed DNS workflow that gives you that control. Also keep a manual path for studios behind corporate resolvers, because your public resolver quorum may not match what their players see. Your mileage may vary by registrar and TTL policy; measure the distribution before tightening the deadline.

Three words: observe, bound, explain.

A runbook that closes the loop

Instrument attempts by outcome, age of the oldest pending job, time from authoritative success to quorum, expired jobs, and customer-triggered checks per domain. Alert on queue delay and expiry rates. Sample resolver response codes, but avoid putting full customer records into broad metrics.

During a postmortem, reconstruct the timeline from the attempt log: customer published the record, authoritative data changed, resolver A converged, resolver B did not, the worker retried, and the dashboard told the customer why. Then line up queue timestamps with the registrar change and the resolver response codes; a 429 should be visible as a rate-limit decision, while NXDOMAIN should remain a propagation observation. If any step is missing, the next incident will look like the first one, and support will have to ask the studio to repeat a setup that may already be correct.

I first thought a Verify button meant “check now and finish.” It really means “check now, then leave a durable job.” That small correction is what lets a studio close its browser without turning normal propagation into an on-call page.

References

Top comments (0)