# How it works

> How Custom Domain API verifies a customer's hostname, issues its certificate and routes each request to the right workspace, and what each domain status and check means.

Source: https://customdomainapi.com/docs/how-it-works/

## The path of a request

1. **TLS.** A visitor opens `https://forms.acme.com`. The customer's CNAME
   sends them to the edge, which presents a certificate for that exact
   hostname. Certificates are issued from Let's Encrypt on demand, only for
   hostnames that passed verification, and renewed automatically.
2. **Route.** The edge finds the application the hostname belongs to. A
   hostname that belongs to no application gets a 404 and never reaches an
   origin.
3. **Assert.** The edge removes any `X-Custom-Domain-*` header the visitor
   sent, checks that the domain may be served right now, and adds a fresh
   signed `X-Custom-Domain-Assertion` naming your application, the domain
   and the workspace `reference`.
4. **Proxy.** The request goes to your origin with `Host` set to the
   customer's hostname and `X-Forwarded-Proto: https`, so absolute URLs,
   cookies and redirects your app builds are correct.

Your origin verifies the assertion and selects the workspace from its
`reference` ([Verifying requests](/docs/verify-requests/)). It never trusts
the `Host` header to pick a tenant.

## The four checks

A domain goes live only when all four checks pass. They are re-checked
regularly, and serving stops the moment one fails.

| Check | Passes when |
|---|---|
| `ownership` | The TXT record `_custom-domain-challenge.<hostname>` holds this registration's token. |
| `routing` | The hostname's CNAME chain ends at your edge name (on Free, `edge.customdomainapi.com`). |
| `certificate` | The edge serves a valid certificate for the hostname. |
| `origin` | Your origin is verified, and answers `GET /.well-known/custom-domain-workspace` through the edge with the workspace it selected from the assertion. |

Each check has a `status` (`pending`, `passing` or `failing`) and, when
failing, a stable `error_code` and a `message` in plain words:

| Check | `error_code` | Meaning |
|---|---|---|
| ownership | `txt_record_not_found` | No TXT record at that name yet. |
| ownership | `txt_token_mismatch` | A TXT record exists, but not with this registration's value. |
| ownership | `txt_token_stale` | The TXT holds the token of an earlier registration. |
| routing | `cname_not_found` | No CNAME, or A/AAAA records instead of a CNAME. |
| routing | `cname_target_mismatch` | The CNAME points somewhere else. |
| either | `dns_timeout` | DNS didn't answer; retried automatically. |
| origin | `origin_not_ready` | No verified origin yet. |
| origin | `workspace_probe_failed` | The workspace path didn't answer 200. |
| origin | `workspace_probe_invalid` | It answered 200 without a JSON `reference`. |
| origin | `workspace_mismatch` | The origin selected another workspace: it isn't using the assertion. |

Failing checks are retried after 1, 2, 5, 15 and 30 minutes, then hourly;
passing ones every 6 hours. Ask for an early re-check with
`POST /v1/domains/{id}/checks` after your customer fixes a record.

## Statuses

| Status | Meaning | Served |
|---|---|---|
| `pending_dns` | Waiting for the TXT and CNAME records. | no |
| `provisioning` | Records verified; certificate and origin checks running. | no |
| `ready` | All four checks pass. | yes |
| `attention_required` | A check that passed is failing again (often a changed CNAME). | no |
| `suspended` | Ownership lost for 24 hours, or suspended by policy. | no |
| `deleting` | Deleted. The hostname can be registered again at once. | no |

`ready`, `attention_required`, recovering to `ready`, and deletion each send
a [webhook](/docs/webhooks/).
