# Domain Acquisition Hooks

https://dev.wisecp.com/es/domain-acquisition-hooks

The eight hooks on the path to the domain provider: the gates before a register, transfer or renew call, and the events behind them.

## Overview

Getting a domain is a **paid and irreversible** call. Once the provider opened the registration there is no taking it back, so every step of this path has a gate in front of it and an event behind it.

Gates run **before** the call and can stop it. Events run **after**, separately for success and for failure. Below is the full contract of all eight: which values arrive, in what order, and what you have to return.

## Reference

### Stopping a register or transfer request

gatedomain.create

`RegistrarModule::create()` before a paid call

Runs immediately before the register or transfer request reaches the provider; the method was checked and the arguments are not built yet.

Parameters 3

$servicearrayThe service record being provisioned. Owner, product and term live here.

$optionsarrayThe parameters going to the provider: `sld`, `tld`, `year`, `dns`, `whois`, `tcode`.

$methodstringThe method about to run: `register` or `transfer`. With a transfer code present it is a transfer.

Return 1

stringA non-empty string **stops** the action; the text is thrown as the error and the provider is never called. `null` or an empty string lets it carry on.

Listener PHP

```php
Hook::add('gate:domain.create', 10, function ($service, $options, $method) {
    if ($method === 'transfer') return null;              // leave transfers alone

    $years = (int) ($options['year'] ?? 1);
    if ($years > 5) return 'We register at most 5 years at a time.';

    return null;
});
```

### Taking the register or transfer result

actiondomain.created

`RegistrarModule::create():122` once per call

Runs right after the provider call returned. It is **no promise of success**: the result value can carry a failure too.

Parameters 3

$servicearrayThe service record being provisioned.

$resultarray|boolWhat the provider returned: a config and status array, or `false` on failure. This is the value to read first.

$methodstringThe method that ran: `register` or `transfer`.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:domain.created', 10, function ($service, $result, $method) {
    if ($result === false) return;                        // failure has its own hook

    if ($method === 'register') Dns::applyTemplate($service['name'] ?? '');
});
```

### Catching a failed registration

actiondomain.register_failed

`RegistrarModule::create()` on failure only

Runs where the provider call came back a failure, right after the previous hook.

Parameters 4

$servicearrayThe service record the provisioning was attempted for.

$optionsarrayThe parameters that were sent to the provider.

$methodstringThe method that ran.

$errorstringThe error message the module reported. It can be empty.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:domain.register_failed', 10,
    function ($service, $options, $method, $error) {
        Ops::alert('domain-provision', [
            'name'   => $service['name'] ?? '',
            'method' => $method,
            'error'  => $error ?: 'the provider gave no reason',
        ]);
    });
```

### Stopping a renewal request

gatedomain.renew

`RegistrarModule::renew():144` before a paid call

Runs before the renewal request reaches the provider.

Parameters 2

$servicearrayThe service being renewed. Its end date and term are in here.

$optionsarrayThe renewal parameters: `sld`, `tld`, `year`.

Return 1

stringA non-empty string stops the renewal. This hook is also on the path of overnight jobs — stopping it leaves the domain unrenewed.

Listener PHP

```php
Hook::add('gate:domain.renew', 10, function ($service, $options) {
    // Renewing spends money where the customer already asked to cancel.
    if (Acme::cancelRequested((int) ($service['id'] ?? 0)))
        return 'A cancellation is open, so the renewal was stopped.';

    return null;
});
```

### Learning that a renewal landed

actiondomain.renewed

`Invoices::handle_renewal_domain_paid():1040` from the payment path

Runs after a paid renewal item extended the end date and the provider was updated.

Parameters 1

$servicearrayThe renewed service record. The end date is the **new** one.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:domain.renewed', 10, function ($service) {
    ExternalDns::syncExpiry($service['name'] ?? '', $service['duedate'] ?? '');
});
```

### Catching a failed renewal

actiondomain.renew_failed

`RegistrarModule::renew()` expiry at risk

Runs where the provider's renewal call came back a failure.

Parameters 3

$servicearrayThe service the renewal was attempted for.

$optionsarrayThe renewal parameters.

$errorstringThe error message the module reported; it can be empty.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:domain.renew_failed', 10, function ($service, $options, $error) {
    // The customer paid and the domain did not renew: this wants a person.
    Ops::page('domain-renew-failed', $service['name'] ?? '', $error);
});
```

### Following a transfer through

actiondomain.transfer.completed

`Services::check_transfer_status():686` the service is live now

Runs after the provider reported the transfer done, the service went live and word was sent.

Parameters 1

$servicearrayThe service record **read afresh** after activation; its start and end dates are current.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:domain.transfer.completed', 10, function ($service) {
    ExternalDns::provision($service['name'] ?? '');
});
```

### Catching a failed transfer check

actiondomain.transfer.failed

`Services::check_transfer_status():659` the check repeats

Runs where the provider's transfer status check failed, right before the error is thrown.

Parameters 1

$servicearrayThe domain service whose transfer check failed.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:domain.transfer.failed', 10, function ($service) {
    // The check repeats on a schedule: alert on a streak, not on one failure.
    if (Acme::failStreak((int) ($service['id'] ?? 0)) >= 3)
        Ops::alert('domain-transfer-stuck', $service['name'] ?? '');
});
```

### Following the transfer code being saved

actiondomain.transfer_authcode_saved

`ClientDomains` secret value

Runs after a customer saves the authorisation code for an incoming transfer. This is where you tell the outside system that drives the transfer.

Parameters 2

$servicearrayThe domain record.

$codestringThe saved authorisation code, **in the clear**. Whoever holds this code can move the domain elsewhere: keep it out of your own records and out of your logs.

Return 1

voidThe return is ignored. The code is already saved.

Listener PHP

```php
Hook::add('action:domain.transfer_authcode_saved', 10, function ($service, $code) {
    // Carry the fact that a code arrived, NOT the code itself.
    Acme::transferReady((int) ($service['id'] ?? 0), $service['name'] ?? '');
});
```

## Pitfalls

> **The result hook is no promise of success**
> 
> The register hook runs when the provider call **returned**, not when it succeeded. Where the second parameter is `false` the work **did not land**. Setting up DNS or telling the customer "your domain is ready" without checking treats a record that does not exist as real.

> **The renewal gate is also on the overnight path**
> 
> Renewals do not run from an operator's hand alone: the post-payment flow and scheduled jobs pass the same gate. A condition you put there can leave a domain **unrenewed** with nobody noticing. Record it somewhere when you stop the gate; a silent veto is the costliest kind.

> **The transfer check repeats**
> 
> The transfer status is asked on a schedule, and every failed check runs the failure hook **again**. A listener that alerts on each call produces dozens of messages for one pending transfer. Count the streak rather than reacting to a single failure.

> **The gate is the last stop before a paid call**
> 
> The register and renew gates are the **last** point before money is spent. Fraud checks, quota limits and premium confirmations belong here. The same check made after the call does not bring the money back.

## Related Articles

- [Service Lifecycle Hooks](https://dev.wisecp.com/en/service-lifecycle-hooks)
- Invoice and Payment Hooks
- [Writing a Hook Listener](https://dev.wisecp.com/en/writing-a-hook-listener)
