DomainProof
Concepts

How verification works

The claim → challenge → check → verified lifecycle, its five states, what a check actually looks for, and how background rechecking keeps a domain's status honest over time.

Every domain claim moves through a small state machine. This page covers what each state means, what a check actually looks for, what moves a domain between states, and how the background scheduler keeps a claim's status current without you having to poll for it.

The five states

not_started --(claimed)--> pending --(check passes)--> verified
                              |                            |
                    (window expires)                (record disappears)
                              v                            v
                            failed <--(grace expires)-- temporarily_failed
                              ^                            |
                              |                    (record recovers)
                       (regenerate) <--------------------- + back to verified
  • not_started — a domain that hasn't been claimed yet. You never see this in an API response; it's the state before a claim exists.
  • pending — a challenge has been issued and is waiting for its first passing check. Every fresh claim starts here.
  • verified — the most recent check found the expected record. This is the steady state for a healthy claim.
  • temporarily_failed — a previously verified domain's record disappeared or changed on a recheck. This is a 72-hour grace window, not an immediate failure — the domain still shows as reversible.
  • failed — either a pending domain's verification window elapsed with no correct record ever appearing, or a temporarily_failed domain's grace window closed without recovering.

What a check looks for

A check queries the claimed hostname's own authoritative nameservers — never a resolver cache, so a stale intermediate cache can't report a false negative — for a TXT record at _domainproof-challenge.<your-domain>. That name is rooted at the exact hostname you claimed, not its registrable domain: a record at _domainproof-challenge.dashboard.acme.com is a child label of dashboard.acme.com, never a record placed at acme.com itself. That matters for two reasons — publishing it can never conflict with an existing CNAME at the apex or a subdomain you're claiming, and it only proves control of the specific subdomain you claimed, not the whole eTLD+1.

The record's domainproof-verify= prefix namespaces the token, so an unrelated TXT record at the same host (SPF, DKIM, another verification product) is simply ignored rather than treated as a conflict — a check succeeds the moment any TXT record at that hostname parses with a matching token. See the Quickstart for the full record shape a claim returns.

What moves a domain between states

FromEventTo
not_startedDomain is claimed, issuing a challengepending
pendingA check finds the expected recordverified
pendingThe 72-hour verification window elapses with no correct recordfailed
pendingThe challenge is regeneratedpending (fresh token, window restarts)
verifiedA recheck still finds the expected recordverified (no-op refresh)
verifiedA recheck no longer finds the expected recordtemporarily_failed
temporarily_failedA recheck finds the expected record againverified
temporarily_failedThe 72-hour grace window elapses with no recoveryfailed
failedThe challenge is regeneratedpending (fresh token, verification restarts from scratch)

A failed domain never recovers on its own — claim a fresh challenge with POST /v1/domains/:id/regenerate to restart verification (see Claim and verify a domain).

The verification window

A freshly issued challenge has 72 hours to pass its first check. If nothing verifies it in that window, the domain moves straight to failed — not because a check actively failed, but because the window closed. Calling POST /v1/domains/:id/verify on an expired pending domain returns that outcome as expired, with a plain-language explanation telling the caller to claim the domain again for a fresh token.

The temporarily_failed grace window is a separate 72-hour clock with the same duration, but a different meaning: it only starts once a domain has already verified and then lost its record. The two windows are tracked independently, even though they happen to be the same length today.

Automatic re-checking

You never have to poll /verify to keep a domain's status current — a background worker re-checks every domain on its own schedule, running the exact same check/transition/publish path the API endpoint does:

StatusRecheck cadence
pendingBacks off as checks keep coming back empty: 1 minute, 5 minutes, 15 minutes, 1 hour, then every 6 hours from then on.
verifiedEvery 24 hours — a slow safety net, since verified domains rarely change.
temporarily_failedEvery 15 minutes, fast enough to catch a quick recovery well before the 72-hour grace window closes.
failedNever — a failed domain only moves again once you regenerate its challenge.

Calling POST /v1/domains/:id/verify yourself is always safe (it re-runs the check every time) and useful for anything that wants an immediate answer — a demo, a "check now" button, a CI step waiting on a claim — but it's never required for a correctly configured domain to eventually show as verified.

How verification works — DomainProof docs