Registering a domain
In this tutorial, we’ll cover how to register domains on behalf of your users (or organization). We’ll discuss how to gather contact information required for domain registration, create a purchase order with the collected contact information, and track the domain registration progress – allowing you to keep your users in the know.
How it works at a high level
- The user requests a domain registration.
- Your application checks the domain’s availability.
- You construct and gather the contact details required for domain registration.
- A registration order is created for the domain. Key information is persisted in your application.
- Notify your customer when the domain is registered and online.
The tutorial is written in Ruby. The code provided can be part of a stand-alone script. Alternatively, popular frameworks such as Sinatra, Ruby on Rails, Hanami, etc. can integrate the code as a service.
Prerequisites
For this tutorial, you’ll need the following dependencies:
- DNSimple Ruby Client
- Public suffix parser for ruby
- DNSimple API Access Token
- Your DNSimple account number – you can find it in the URL when visiting a domain, or when the API Access Token was issued.
Configuring the API client
You’ll need to interact with the API extensively, so it’s a good idea to create an adapter to encapsulate the interaction between your system and our API.
require 'dnsimple'
class DnsimpleAdapter
SANDBOX_ENDPOINT = 'https://api.sandbox.dnsimple.com'.freeze
PRODUCTION_ENDPOINT = 'https://api.dnsimple.com'.freeze
class << self
attr_accessor :api_access_token, :account_id, :endpoint
# Set your configuration with block
def configure
yield(self)
end
# Returns the configuration hash
#
# @return [Hash]
def config
@config ||= {
api_access_token: api_access_token,
account_id: account_id,
endpoint: endpoint || SANDBOX_ENDPOINT
}.freeze
end
# Returns a configured DNSimple API client
def client
Dnsimple::Client.new(
base_url: config[:endpoint],
access_token: config[:api_token]
)
end
end
end
Now you can initialize the adapter with your API Access Token and Account number.
DnsimpleAdapter.configure do |config|
config.api_access_token = "<your access token>"
config.account_id = "<your account number>"
end
Checking the domain’s availability
When a user wants to register a domain, the first step is to check if the domain is available for registration.
To do this, add a method to your adapter that wraps around the API’s check_domain
call.
def check_domain(domain)
client.registrar.check_domain(config[:account_id], domain).data
end
Now we can check if the domain is avialable by making a request and examining the response:
domain_name = "makeideashappen.com"
domain = DnsimpleAdapter.check_domain(domain_name)
raise 'Domain is not available' unless domain.available
If the requested domain is not available, we raise an error as a way to stop the execution of our code. However, it’s a good idea to inform your customers that the domain is taken, and they should explore other options.
Gathering extended attributes for the domain
Some ccTLDs, like .FR, .ASIA, .EU, etc. require additional information to register the domain.
To do this, we can add a method to our adapter that makes a call and gets the extended attributes for a TLD.
def tld_extended_attributes(tld)
client.tlds.tld_extended_attributes(tld).data
end
This endpoint requires only the TLD be provided, so you need to extract it from your customer’s input. There are some helpful libraries you can use instead of rolling out your own custom implementation.
require 'public_suffix'
domain_name = PublicSuffix.parse("makeideashappen.eu")
domain_name.tld # => 'eu'
extended_attributes = DnsimpleAdapter.tld_extended_attributes(domain_name.tld)
If extended_attributes
is an empty array, there are no extended attributes to present to the user. However, if there are attributes, they will have the following structure:
{
'name' => 'attribute-name',
'title' => 'Title explaining what the field requires',
'description' => 'Optional description elaborating on what and why this field is needed',
'required' => true,
'options' => [
{
'title' => 'Name of option',
'value' => 'accepted-value',
'description' => 'Optional description'
}
]
}
-
required
indicates if the field MUST be filled in or if it can be left blank. -
options
if it’s a blank array[]
, it means the user must provide the value.
For example .EU will return:
{ 'name' => 'x-eu-registrant-citizenship',
'title' => 'Private Registrant European Citizenship',
'description' =>
'In case the registrant is a private person, it is possible to register a .EU domain even though the registrant is not based in the EU. The registrant however must have an European Citizenship in these cases.',
'required' => false,
'options' =>
[{ 'title' => 'Austrian', 'value' => 'at', 'description' => nil },
{ 'title' => 'Belgian', 'value' => 'be', 'description' => nil },
{ 'title' => 'Bulgarian', 'value' => 'bg', 'description' => nil },
{ 'title' => 'Croatian', 'value' => 'hr', 'description' => nil },
{ 'title' => 'Cyprian', 'value' => 'cy', 'description' => nil },
{ 'title' => 'Czech', 'value' => 'cz', 'description' => nil },
{ 'title' => 'Danish', 'value' => 'dk', 'description' => nil },
{ 'title' => 'Dutch', 'value' => 'nl', 'description' => nil },
{ 'title' => 'Estonian', 'value' => 'ee', 'description' => nil },
{ 'title' => 'Finnish', 'value' => 'fi', 'description' => nil },
{ 'title' => 'French', 'value' => 'fr', 'description' => nil },
{ 'title' => 'German', 'value' => 'de', 'description' => nil },
{ 'title' => 'Greek', 'value' => 'gr', 'description' => nil },
{ 'title' => 'Hungarian', 'value' => 'hu', 'description' => nil },
{ 'title' => 'Irish', 'value' => 'ie', 'description' => nil },
{ 'title' => 'Italian', 'value' => 'it', 'description' => nil },
{ 'title' => 'Latvian', 'value' => 'lv', 'description' => nil },
{ 'title' => 'Lithuanian', 'value' => 'lt', 'description' => nil },
{ 'title' => 'Luxembourger', 'value' => 'lu', 'description' => nil },
{ 'title' => 'Maltese', 'value' => 'mt', 'description' => nil },
{ 'title' => 'Polish', 'value' => 'pl', 'description' => nil },
{ 'title' => 'Portuguese', 'value' => 'pt', 'description' => nil },
{ 'title' => 'Romanian', 'value' => 'ro', 'description' => nil },
{ 'title' => 'Slovak', 'value' => 'sk', 'description' => nil },
{ 'title' => 'Slovenian', 'value' => 'si', 'description' => nil },
{ 'title' => 'Spanish', 'value' => 'es', 'description' => nil },
{ 'title' => 'Swedish', 'value' => 'se', 'description' => nil }] }
Now that you know how to handle extended attributes, it’s a good time to present them to your customer. Make sure to validate the presence of any required attributes. This is important because domain registration isn’t possible without them.
Creating a contact for your user
For a domain to be registered, it needs to have an associated contact in our system. You’ll probably want to provide your customer’s contact details instead of your own, so the contact will need to be created in our system.
def create_contact(contact_details)
client.contacts.create_contact(config[:account_id], contact_details).data
end
To create the contact, you can call the create_contact
method with your customer’s details.
customer_contact_details = {
"email": 'john.smith@example.com',
"first_name": 'John',
"last_name": 'Smith',
"address1": '123 Main Street',
"city": 'New York',
"state_province": 'NY',
"postal_code": '11111',
"country": 'US',
"phone": '+1 321 555 4444'
}
contact = DnsimpleAdapter.create_contact(customer_contact_details)
You can see all available contact fields here.
Now that you have a contact available for your customer, store the contact.id
in your system for easy programmatic access, since you’ll need it later.
Registering a webhook
Before we proceed to registering the domain, make sure you have a webhook registered and listening to events to synchronize your local state to the domain’s state. It can take some time for the domain to be fully registered, and webhooks help you notify your customer when the registration is complete.
def register_webhook(url)
client.webhooks.create_webhook(config[:account_id], { url: url }).data
end
You will likely only ever need to register a single webhook for your system. For this tutorial, we can use a third-party service RequestBin to provide us with a visualisation of the events we would expect to receive from our system. You can also use secure local proxy tunneling, like Ngrok, while developing and testing your webhook integration.
url = '<url of HTTP event source>'
DnsimpleAdapter.register_webhook(url)
Registering the domain
Now that we’ve prepared all the required components, we can register the domain.
- Domain name that’s available for registration.
- The TLD’s extended attributes – if applicable.
- Customer contact in our system.
- Registered webhook for us to receive updates on account activity and resource states (Optional).
We can create a method in our adapter to allow us to register domains via the API client.
def register_domain(domain, registrant_id, whois_privacy: false, auto_renew: true, extended_attributes: {})
client.registrar.register_domain(
config[:account_id],
domain,
{
registrant_id: registrant_id,
whois_privacy: whois_privacy,
auto_renew: auto_renew,
extended_attributes: extended_attributes
}
).data
end
We can register a user’s chosen domain using all the previously-obtained information.
domain_name = 'makeideashappen.com'
domain_registration = DnsimpleAdapter.register_domain(domain_name, contanct.id)
We didn’t include any extended attributes, because there are none for .COM. The domain registration will hold the domain’s ID in our system (which you may want to store for later use, and possibly associate it with your users).
The webhook you registered will receive updates on the domain’s registration status. It can take some time before the domain is registered, and it’s a good way to keep your system synced. The two events you’ll be interested in when registering a domain are domain.register:started and domain.register.
Here’s the complete adapter after all the additions we made throughout the tutorial:
require 'dnsimple'
require 'public_suffix'
class DnsimpleAdapter
SANDBOX_ENDPOINT = 'https://api.sandbox.dnsimple.com'.freeze
PRODUCTION_ENDPOINT = 'https://api.dnsimple.com'.freeze
class << self
attr_accessor :api_token, :account_id, :endpoint
# Set your configuration with block
def configure
yield(self)
end
# Returns the configuration hash
#
# @return [Hash]
def config
@config ||= {
api_token: api_token,
account_id: account_id,
endpoint: endpoint || SANDBOX_ENDPOINT
}.freeze
end
# Returns a configured DNSimple API client
def client
Dnsimple::Client.new(
base_url: config[:endpoint],
access_token: config[:api_token]
)
end
def check_domain(domain)
client.registrar.check_domain(config[:account_id], domain)
end
def create_contact(contact_details)
client.contacts.create_contact(config[:account_id], contact_details)
end
def tld_extended_attributes(tld)
client.tlds.tld_extended_attributes(tld)
end
def register_domain(domain, registrant_id, whois_privacy: false, auto_renew: true, extended_attributes: {})
client.registrar.register_domain(
config[:account_id],
domain,
{
registrant_id: registrant_id,
whois_privacy: whois_privacy,
auto_renew: auto_renew,
extended_attributes: extended_attributes
}
).data
end
def register_webhook(url)
client.webhooks.create_webhook(config[:account_id], { url: url }).data
end
end
end