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

  1. The user requests a domain registration.
  2. Your application checks the domain’s availability.
  3. You construct and gather the contact details required for domain registration.
  4. A registration order is created for the domain. Key information is persisted in your application.
  5. 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:

 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