---
title: How Domain Registration Works in RackHelm
description: "RackHelm sells domain names as ordinary products: one product per TLD, priced through billing cycles, bound to a registrar integration. When the invoice is paid, a background job calls the registrar's API and registers or transfers the name. This article…"
url: "https://rackhelm.com/documentation/how-domain-registration-works/"
updated: "2026-09-24"
category: "Integrations: Domains & SSL"
---

# How Domain Registration Works in RackHelm

RackHelm sells domain names as ordinary products: one product per TLD, priced through billing cycles, bound to a registrar integration. When the invoice is paid, a background job calls the registrar's API and registers or transfers the name. This article explains the moving parts so the registrar-specific articles make sense, and is written for operators setting the system up.

> **Note:** Configuring registrars requires the `edit_integrations` permission. Importing existing domains requires `edit_company_settings`. See [Roles & Permissions](/docs/roles-and-permissions/).

## The six supported registrars

RackHelm ships six registrar drivers. They are declared in one place in the code — `Product::REGISTRAR_INTEGRATIONS` — and every screen that has to ask "is this a domain?" reads that same list:

| Provider key | Registrar | Protocol |
| --- | --- | --- |
| `enom` | [eNom](/docs/registrar-enom/) | HTTP query API (XML) |
| `namesilo` | [NameSilo](/docs/registrar-namesilo/) | HTTP query API (XML) |
| `namecheap` | [Namecheap](/docs/registrar-namecheap/) | HTTP query API (XML) |
| `nominet` | [Nominet](/docs/registrar-nominet/) (.uk registry) | EPP over TLS, TCP port 700 |
| `opensrs` | [OpenSRS](/docs/registrar-opensrs/) | XCP envelopes over HTTPS:55443 |
| `realtimeregister` | [Realtime Register](/docs/registrar-realtime-register/) | REST/JSON |

> **Warning:** Only the eNom driver has been exercised against a live reseller account. The Namecheap, Nominet, OpenSRS and Realtime Register drivers were written against each registrar's published API but have **not** been verified end-to-end with a real account. Run every one of them in its test environment and place at least one real order before you put a TLD on sale.

## How a domain order flows

1. The customer searches for a name. RackHelm asks the registrar bound to each TLD product whether the name is available, batching all TLDs that share a registrar panel into one bulk call.
2. They land on the configure page for the chosen TLD product. Three hidden order variables are set there: `DomainName` (the second-level label), `DomainAction` (`register` or `transfer`), and — for a transfer — `EppCode`.
3. The item goes through checkout like any other product and sits at `provisioning_status = pending`.
4. Once the invoice is paid, the scanner picks the item up and dispatches `ProvisionOrderItem`.
5. The job resolves the product's active integration, builds the matching driver, and calls either `transfer()` or `provision()`.

The register-versus-transfer decision is made in the job, not by the customer's word alone. The item is treated as a transfer when an `EppCode` (or `AuthCode`) variable is present *or* when `DomainAction` is `transfer`. The second condition matters for .uk: Nominet transfers carry no auth code at all, so a blank code with `DomainAction=transfer` is a legitimate transfer order rather than a registration attempt.

![The Integrations page with a Domain Registrars section of provider cards](/wp-content/uploads/rackhelm-docs/integrations-domains-integrations-index.png)
Every registrar appears as a card under **Domain Registrars**, showing whether it is configured and active.

## What a registrar driver has to provide

All six drivers implement the same two contracts, so the rest of the platform never needs to know which registrar is behind a domain.

| Method | What it does |
| --- | --- |
| `provision()` | Registers the name, sending the registrant contact from the order's address (falling back to the user profile), for the number of whole years the billing cycle buys. |
| `transfer()` | Starts an inbound transfer and returns metadata including `transfer_initiated`. |
| `renew()` | Extends the registration at the registrar. |
| `getDomainDetails()` | Reads live expiry, lock state, status and nameservers. Throws when the registrar is unreachable — callers treat that as "unknown", never "unchanged". |
| `getYearsFromBillingCycle()` | The single authority on the term. Reads the item's cycle, falls back to the order's, floors to whole years with a minimum of 1. |
| `setNameservers()` / `setRegLock()` | Drive the customer's domain management page. |
| `suspend()` / `unsuspend()` | Not implemented on any registrar — domains have no suspend concept. |

Optional methods vary by registrar: `getEppCode()`, `enablePrivacy()`, `checkDomains()` (bulk availability) exists on eNom, Namecheap, Nominet, OpenSRS and Realtime Register, and `listAllDomains()` on all of those except eNom, which has its own bespoke listing path. NameSilo's driver has none of them.

## Registration term and pricing

The term is derived from the billing cycle, not entered separately. A cycle of 12 months buys one year, 24 months buys two, and anything under 12 months is floored to one year because registries do not sell monthly terms. Transfer orders are priced from the billing cycle's `transfer_price`; registrations use `price`; renewals use `renewal_price`.

## Nameservers and DNS

Nominet and Realtime Register let you set a comma-separated **Default Nameservers** list on the panel, applied to new registrations only. eNom registers with its own DNS by default. Customers can change nameservers afterwards from their domain page; the change is queued as a background job rather than applied inline, so the page returns immediately and the registrar call happens behind it.

In-portal DNS record editing exists only for eNom, and only while the domain is still on eNom's own `name-services.com` nameservers — the DNS tab is hidden otherwise, because a domain delegated elsewhere is not managed by the registrar.

## WHOIS privacy

Privacy is requested by setting a `DomainPrivacy` order variable on the domain product. What that produces depends on the registrar: eNom adds its ID Protect service, NameSilo sets `private=1` at registration, Namecheap enables the WhoisGuard subscription attached to the domain, OpenSRS sets `f_whois_privacy` at registration (and `whois_privacy_state` afterwards), and Realtime Register sends `privacyProtect`. Nominet is the exception — .uk WHOIS hides registrant details by default post-GDPR, so there is nothing to buy and the request is logged and ignored.

## Auto-renew and the renewal job

Domains renew through RackHelm's normal billing cycle: an invoice is generated, and when it is paid a `RenewOrderItemAtRegistrar` job calls the registrar's `renew()`. That job is deliberately careful about money:

- It is driven by a renewal row created under a unique index, so it cannot be run twice for one invoice.
- It records the registry expiry *before* the first attempt. Every later attempt re-reads the registry and only calls `renew()` if the expiry has not already moved — a registrar can commit a renewal and then time out, which from our side looks identical to failure.
- It allows 4 attempts with 1, 5 and 15 minute backoffs, but a message matching a terminal condition (insufficient funds, redemption, domain not found, invalid TLD, authentication failed) fails immediately instead of retrying.
- On permanent failure it opens a high-priority billing ticket on the customer's account and raises the `domain.renewal_failed` admin notification. Billing is never reversed — the invoice stays paid and the due date stays advanced.

## The transfer sync job

An inbound transfer does not complete when the order is placed; the registry takes days. When a driver starts a transfer it writes `transfer_initiated: true` into the item's `provisioned_meta` and RackHelm splits the order so the transferred name gets its own billing record.

The scheduled job **Domain Updater** (class `SyncPendingEnomTransfers`) then reconciles them. Despite the historical name it covers every registrar: it finds items flagged `transfer_initiated`, calls `getDomainDetails()`, and when a real expiry comes back it clears the flag, writes the registry expiry to the order item and sets the order's due date to match. It is **disabled** by default — enable it under **Schedules → Domain Updater**, or run it manually with:

```bash
php artisan enom:sync-transfers
```

## Keeping the local picture in step

A second job, `SyncProviderDomains`, pulls the full domain list from each configured registrar into the `provider_domain_caches` table. That cache does three jobs: it feeds the domain importer, it warms the customer-facing details cache, and it detects domains that have been transferred away. Run it with:

```bash
php artisan domains:sync-providers
```

> **Warning:** The transferred-away detector cancels a local service when its cached row has not been refreshed for 48 hours. It is guarded — it skips entirely if nothing synced in the last two hours, and skips any registrar with no recent successful sync — but if you run this job irregularly those guards are what stand between a stale cache and cancelled customer services. Schedule it on a fixed cadence or not at all.

## Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| Item stuck at "processing" with the message "No integration is bound to …" | The product has no active integration row. Bind one on the product's **Provisioning** tab, then retry from **Users & Roles → Pending Provisioning**. |
| Registration failed with "Missing SLD or TLD" | The order item has no `DomainName` variable, or the product is not named after its TLD. Domain products must be named `.com`, `.co.uk` and so on — the driver strips the leading dot to build the name. |
| Every call to one registrar fails authentication | Almost always IP allowlisting. eNom, NameSilo, Namecheap, OpenSRS (production) and Nominet all require this server's outbound IP to be registered with them. Set the address in **Platform Settings → General & System → External IP** as well, since eNom and Namecheap send it as a request parameter. |
| A transfer never completes | The **Domain Updater** schedule is disabled by default. Enable it under **Schedules**, or run `php artisan enom:sync-transfers`. |
| Renewal invoice paid but the domain still expires | Check the ticket the failed-renewal path opens on the customer's account, and the `[DomainRenewal]` lines in `storage/logs/laravel.log`. Renewals that cannot be retried safely stop deliberately rather than risk buying a second term. |
| Availability search returns nothing for some TLDs | Only the requested TLD plus a fixed priority list are checked in a group search, and a TLD product with no bound registrar panel is skipped entirely. |
