Set up a custom payment domain
A payment domain is a hostname you control, such as pay.example.com, where
drivers pay on Ivora's checkout, charging status and receipt pages with your
branding. Once it is your project's
checkout_domain and ready, it becomes your checkout_origin: the origin of
your checkout links, QR codes and charger screens, and the page hosted checkout
returns drivers to.
Payment domains use manual DNS in production: you add two records by hand, and no DNS provider sign-in is offered.
Before you start
- Set your branding first, so the pages show your name and logo as soon as the domain is ready.
- Choose a hostname that serves nothing else, such as
pay.example.com. A hostname serves one purpose: OCPP, payment, QR or return. Registering it for a second purpose is refused (409) until you disable the first. - Ivora terminates TLS for the hostname with its own certificate, so nothing may sit in front of it: no CDN, proxy or web application firewall.
- Changes need
stations:write(a tenant administrator); reads needstations:read.
Ivora serves only the driver pages on it: /checkout/…, /charging/… and
/receipt/… for your own chargers, the files those pages load and the API they
call. Every other path, a page for a charger that is not yours and any write to
a page answer 404.
1. Register the hostname
POST /v1/tenants/{tenant_id}/payment-domains
Content-Type: application/json
{"hostname": "pay.example.com"}
The request is idempotent by tenant and hostname. The response
(201 Created) lists the two DNS records the domain needs and the checkout URL
a QR code encodes on it:
{
"id": "dom_7a1e...",
"purpose": "payment",
"hostname": "pay.example.com",
"status": "pending_dns",
"ready": false,
"checkout_url_template": "https://pay.example.com/checkout/{station_name}-{evse_number}",
"dns_records": [
{"type": "CNAME", "name": "pay.example.com", "value": "ocpp-edge.ivoracharge.com", "ttl": 300, "proxied": false},
{"type": "TXT", "name": "_ivora-payment.pay.example.com", "value": "ivora-payment-verification=<generated token>", "ttl": 300}
]
}
Show both records to whoever manages your DNS exactly as returned. Always take them from the response, never from this page: the address record's value is the one Ivora names for your environment.
2. Add the two records
| Type | Full DNS name | Value | Setting |
|---|---|---|---|
| CNAME | pay.example.com | the value returned, such as ocpp-edge.ivoracharge.com | DNS only, TTL 300 or Auto |
| TXT | _ivora-payment.pay.example.com | ivora-payment-verification=<generated token> | TTL 300 or Auto |
The CNAME target is Ivora's stable DNS-only name for its edge. Point the CNAME at it directly, not through another name of yours. An A record to the address it resolves to does not verify: if the edge address changes, Ivora updates its own name and your CNAME keeps working.
Warning
Keep the address record DNS only. In Cloudflare, the proxy status must be the grey cloud ("DNS only"), not the orange cloud ("Proxied"). A proxied record, or any CDN or proxy in front of the hostname, does not verify, stops Ivora from issuing the hostname's certificate and breaks checkout over HTTPS.
- Editors that append your zone automatically use
payand_ivora-payment.payas the names. - Keep both records for as long as you use the domain. Ivora rechecks them every 15 minutes; a domain whose records stop checking out stops serving the pages, and new links fall back to the default payment host.
- Certificate issuance needs public port 80 to reach Ivora (only the certificate challenge is served over HTTP) and any CAA records on your zone to allow Let's Encrypt.
Cloudflare
- In the Cloudflare dashboard, open your zone (
example.com) and go to DNS > Records. - Add record: type
CNAME, namepay, target thevaluereturned, proxy status DNS only (grey cloud), TTL Auto. Save. - Add record: type
TXT, name_ivora-payment.pay, content the TXTvaluereturned. Save.
If your domain is registered elsewhere (for example GoDaddy) but its nameservers are Cloudflare's, add the records in Cloudflare, not at the registrar.
3. Check DNS
POST /v1/tenants/{tenant_id}/payment-domains/{domain_id}/verify
checks reports each record: {"address": true, "ownership": true} once both
are published, and the status moves to dns_verified. Check again at most
every 15 seconds (429 otherwise); DNS changes can take longer than that to
appear.
dns_verified does not mean the pages work yet. Ivora then issues the
hostname's certificate, normally within 15 minutes, and the domain becomes
active with ready: true. Read it with
GET /v1/tenants/{tenant_id}/payment-domains/{domain_id}.
| Status | Meaning |
|---|---|
pending_dns | One or both records do not check out yet |
dns_verified | Both records check out; the certificate is being issued |
active | Certificate installed. ready is true while DNS was rechecked within 24 hours and the certificate has not expired |
disabled | You disabled it; its pages are no longer served |
activation_error explains an activation that needs attention; contact Ivora
support if it stays set.
4. Use it in QR codes and checkout links
GET /v1/tenants/{tenant_id}/branding returns checkout_origin: the origin
of your project's checkout_domain (https://pay.example.com) while it is
ready, or the default payment host. Your first payment domain to become active
is your checkout_domain automatically; choose another one under
Project default domains. Encode
<checkout_origin>/checkout/<station>-<evse> in checkout links and charger
screens, or print sticker_url_template on stickers; Ivora's charger screens
and QR sticker redirects use it already. Hosted checkout with
return_surface: driver returns drivers to the same origin automatically.
When the domain is disabled or stops being ready, checkout_origin and new
checkout returns fall back to the default payment host at once. QR codes you
printed with your own hostname stop working until it is ready again, so keep
the records in place.
Project default domains
PATCH /v1/tenants/{tenant_id} picks which of your domains drivers see. Both
fields take a hostname, or null for Ivora's default; leave a field out to
keep it. GET /v1/tenants/{tenant_id} returns both, and the
sticker_url_template they produce.
| Field | Accepts | Used for |
|---|---|---|
checkout_domain | One of the project's active payment domains | checkout_origin: checkout links, QR codes, charger screens and hosted checkout returns |
qr_domain | One of the project's active QR domains | What new stickers encode: https://<qr_domain>/{station_name} |
curl -X PATCH "$IVORA_API/v1/tenants/$TENANT_ID" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"checkout_domain": "payment.example.com", "qr_domain": "qr.example.com"}'
New stickers use qr_domain first, because a QR domain is a stable redirect
name: its scans follow later payment-domain changes and each charger's
migration. Without one, they use checkout_domain, then Ivora's default.
- Only your own domains of that purpose are accepted (
422 domain_not_in_project). A domain whose certificate is not active and ready yet is refused with409 domain_certificate_inactive; retry oncereadyistrue. - Each change is in the project's audit log as
project.checkout_domain_changedorproject.qr_domain_changed, with the previous and new hostname. - Once you choose (a domain or
null), a payment domain that becomes active later never replaces your choice. - If a chosen domain stops being ready, links and stickers fall back at once (stickers to the checkout domain, then Ivora's default) and use it again when it is ready.
- Ivora's default checkout URLs (
/checkout/<station>-<evse>on the default payment host) keep working for every project, whatever you choose, so codes printed before you set a domain never break.
Removing a default domain
While a domain is the project's checkout_domain or qr_domain, DELETE on it
answers 409 domain_is_project_default. Its details count what still uses
it: qr_stickers (imported stickers whose scans it serves) and
checkout_links (charger screen QR codes on that hostname). Send the request
again with ?confirm=true to remove it; the field becomes null, and the
change is audited with the reason domain_removed.
Open https://pay.example.com/checkout/<station>-<evse> for one of your
chargers to check the page before you print codes.
Disable a domain
DELETE /v1/tenants/{tenant_id}/payment-domains/{domain_id} stops serving the
pages at once; QR codes and checkout returns fall back to the default payment
host. The project's checkout_domain needs ?confirm=true
(Removing a default domain). Your DNS records stay in place. Registering the hostname again starts
over with a new ownership token.
Troubleshooting
| Symptom | Likely cause |
|---|---|
address stays false | The address record is proxied (orange cloud), points somewhere else, or (production) is an A record instead of the CNAME |
ownership stays false | The TXT name carries your zone twice (_ivora-payment.pay.example.com.example.com), or the value is not exactly the one returned |
Stays dns_verified for more than an hour | Port 80 does not reach Ivora, a CDN or proxy is in front of the hostname, or a CAA record does not allow Let's Encrypt |
409 on registration | The hostname is registered for another purpose or verified by another tenant |
A page answers 404 | The domain is not ready (check ready and activation_error), the charger is not yours, or (production) the path is not a driver page |
| Browser shows a certificate error | A proxy in front of the hostname presents its own certificate: set the record to DNS only |



