Domain Contact Hooks

2 vues Markdown

The ten hooks over the person behind a domain, its privacy and its transfer lock.

Overview

These three go together. Contact details are what the registry requires, privacy hides them from outside, and the transfer lock stops the name moving without permission.

All three are written live to the provider. Saved contact profiles, on the other hand, live on our side: they are templates kept so a customer types the same details once, and they carry hooks of their own.

Reference

Stopping a contact write

gatedomain.contacts_save
AdminServices all four roles

Runs before the contact details are written to the provider.

Parameters 2
$servicearrayThe domain service record.
$whoisarrayThe four roles: registrant, administrative, technical, billing. Each role carries name, surname, e-mail, phone and address fields.
Return 1
stringA non-empty string stops the action and the text is thrown as the error. null or an empty string lets it carry on.
Listener
Hook::add('gate:domain.contacts_save', 10, function ($service, $whois) {
    // The registrant e-mail cannot change before it is verified.
    $mail = (string) ($whois['registrant']['EMail'] ?? '');
    if ($mail !== '' && !Acme::verified($mail))
        return 'The registrant e-mail wants verifying first.';

    return null;
});

Following a contact change

actiondomain.contacts_saved
AdminServices on success only

Runs after the contact details reached the provider.

Parameters 2
$servicearrayThe domain service record.
$whoisarrayThe four roles as written to the provider.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:domain.contacts_saved', 10, function ($service, $whois) {
    Audit::whois($service['name'] ?? '', $whois['registrant']['EMail'] ?? '');
});

Stopping a privacy change

gatedomain.privacy_change
AdminServices on and off

Runs before privacy changes at the provider.

Parameters 2
$servicearrayThe domain service record.
$statusstringThe state being asked for: enable or disable. What was asked, not what it became.
Return 1
stringA non-empty string stops the action and the text is thrown as the error. null or an empty string lets it carry on.
Listener
Hook::add('gate:domain.privacy_change', 10, function ($service, $status) {
    // Some extensions offer no privacy at all.
    if ($status === 'enable' && Acme::noPrivacyTld($service['name'] ?? ''))
        return 'This extension offers no privacy.';

    return null;
});

Following the privacy state

actiondomain.privacy_changed
AdminServices the new state is a bool

Runs after privacy changed at the provider.

Parameters 2
$servicearrayThe domain service record.
$enabledboolThe new state: true means privacy is on. Unlike the text value at the gate, this one is a boolean.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:domain.privacy_changed', 10, function ($service, $enabled) {
    Billing::privacyFee((int) ($service['id'] ?? 0), (bool) $enabled);
});

Stopping a transfer lock change

gatedomain.transfer_lock_change
AdminServices on and off

Runs before the lock changes at the provider.

Parameters 2
$servicearrayThe domain service record.
$statusstringThe state being asked for: enable to lock, disable to open.
Return 1
stringA non-empty string stops the action and the text is thrown as the error. null or an empty string lets it carry on.
Listener
Hook::add('gate:domain.transfer_lock_change', 10, function ($service, $status) {
    // Opening the lock is the first step to the name moving away.
    if ($status === 'disable' && Acme::recentlyChangedOwner($service))
        return 'The lock stays on for 60 days after an owner change.';

    return null;
});

Following the lock state

actiondomain.transfer_lock_changed
AdminServices the new state is a bool

Runs after the lock changed at the provider.

Parameters 2
$servicearrayThe domain service record.
$lockedboolThe new state: true means locked.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:domain.transfer_lock_changed', 10, function ($service, $locked) {
    if (!$locked) Ops::watch('domain-unlocked', $service['name'] ?? '');
});

Changing the lock state reported

filterdomain.transfer_lock_status
Hook::runRefs by reference

Runs before the lock state reaches a screen or an API answer.

Parameters 2
$lockedboolrefThe state about to be reported. Where the provider says nothing, you can fill it in here.
$servicearrayThe domain service record.
Return 1
voidThe value changes by reference; the return is not read.
Listener
Hook::add('filter:domain.transfer_lock_status', 10, function (&$locked, $service) {
    // Fill from our own record where the provider does not know.
    if ($locked === null) $locked = Acme::lockMirror((int) ($service['id'] ?? 0));
});

Stopping a transfer code request

gatedomain.epp_code_get
AdminServices the code goes to the owner

Runs before the transfer code is asked of the provider.

Parameters 2
$servicearrayThe domain service record.
$serviceIdintThe service id.
Return 1
stringA non-empty string stops the action and the text is thrown as the error. null or an empty string lets it carry on.
Listener
Hook::add('gate:domain.epp_code_get', 10, function ($service, $serviceId) {
    // The transfer code is the key to the name changing hands.
    if (Acme::openDispute($serviceId)) return 'No code while a dispute is open.';

    return null;
});

Following a transfer code request

actiondomain.epp_code_retrieved
AdminServices two kinds of value

Runs after the code came back from the provider.

Parameters 2
$servicearrayThe domain service record.
$codestring|boolThe code itself, or true. true means "the code was not returned, it was e-mailed to the owner".
Return 1
voidThe return is ignored.
Listener
Hook::add('action:domain.epp_code_retrieved', 10, function ($service, $code) {
    // NEVER log the code: it is the key to the name.
    Audit::note('epp-requested', $service['name'] ?? '',
        $code === true ? 'sent by e-mail' : 'shown on screen');
});

Stopping a contact profile save

gatedomain.whois_profile_save
ClientDomains on our side

Runs before a saved contact profile is written to the database. This one never reaches the provider; a profile is a template kept on our side.

Parameters 3
$uidintThe account id owning the profile. An account arrives here, not a service.
$informationarrayThe contact fields: FirstName, LastName, Company, EMail, Phone, address fields.
$profileIdint0 for a new profile; a value above zero is the id of the one being edited.
Return 1
stringA non-empty string stops the action and the text is thrown as the error. null or an empty string lets it carry on.
Listener
Hook::add('gate:domain.whois_profile_save', 10,
    function ($uid, $information, $profileId) {
        if ($profileId > 0) return null;                  // leave edits alone
        if (Acme::profileCount($uid) >= 10) return 'At most 10 profiles are kept.';
        return null;
    });

Following a profile save

actiondomain.whois_profile_saved
ClientDomains the id is filled in now

Runs after the profile was saved.

Parameters 3
$uidintThe account id.
$profileIdintThe id of the saved profile. Filled in even for a new one.
$informationarrayThe fields that were saved.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:domain.whois_profile_saved', 10,
    function ($uid, $profileId, $information) {
        Acme::indexProfile($uid, $profileId, $information['EMail'] ?? '');
    });

Pitfalls

The transfer code is the key to the name

Anyone holding the code can move the domain to another provider. Do not log the value the event hands you, put it in a notification or send it to an outside system. Where the value is true the code already went to the owner by e-mail; you do not hold it, and you should not.

Text at the gate, a boolean at the event

The privacy and lock gates hand you the state asked for as text (enable/disable), while the events hand you the state reached as a boolean. Mixing them up fails silently: a text value always counts as true.

Profile hooks carry an account, not a service

The first parameter of the saved-profile hooks is an account id, not a service record. A listener used to the other domain hooks reaches for $service['name'] here and finds nothing. A profile belongs to an account, not to a domain.

Changing the registrant can trigger verification

Changing the registrant e-mail starts re-verification on most extensions, and an unverified name can be suspended. A listener touching the contact write should be written knowing that; the verification state has a filter of its own.

Cet article vous a-t-il été utile ?

Merci pour votre retour !

Besoin d'aide supplémentaire ?

Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.