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_integrationspermission. Importing existing domains requiresedit_company_settings. See Roles & 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 | HTTP query API (XML) |
namesilo |
NameSilo | HTTP query API (XML) |
namecheap |
Namecheap | HTTP query API (XML) |
nominet |
Nominet (.uk registry) | EPP over TLS, TCP port 700 |
opensrs |
OpenSRS | XCP envelopes over HTTPS:55443 |
realtimeregister |
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 #
- 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.
- They land on the configure page for the chosen TLD product. Three hidden order variables are set there:
DomainName(the second-level label),DomainAction(registerortransfer), and — for a transfer —EppCode. - The item goes through checkout like any other product and sits at
provisioning_status = pending. - Once the invoice is paid, the scanner picks the item up and dispatches
ProvisionOrderItem. - The job resolves the product’s active integration, builds the matching driver, and calls either
transfer()orprovision().
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.

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_failedadmin 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:
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:
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. |