Domain Contact Hooks
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
Runs before the contact details are written to the provider.
registrant, administrative, technical, billing. Each role carries name, surname, e-mail, phone and address fields.null or an empty string lets it carry on.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
Runs after the contact details reached the provider.
Hook::add('action:domain.contacts_saved', 10, function ($service, $whois) {
Audit::whois($service['name'] ?? '', $whois['registrant']['EMail'] ?? '');
});Stopping a privacy change
Runs before privacy changes at the provider.
enable or disable. What was asked, not what it became.null or an empty string lets it carry on.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
Runs after privacy changed at the provider.
true means privacy is on. Unlike the text value at the gate, this one is a boolean.Hook::add('action:domain.privacy_changed', 10, function ($service, $enabled) {
Billing::privacyFee((int) ($service['id'] ?? 0), (bool) $enabled);
});Stopping a transfer lock change
Runs before the lock changes at the provider.
enable to lock, disable to open.null or an empty string lets it carry on.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
Runs after the lock changed at the provider.
true means locked.Hook::add('action:domain.transfer_lock_changed', 10, function ($service, $locked) {
if (!$locked) Ops::watch('domain-unlocked', $service['name'] ?? '');
});Changing the lock state reported
Runs before the lock state reaches a screen or an API answer.
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
Runs before the transfer code is asked of the provider.
null or an empty string lets it carry on.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
Runs after the code came back from the provider.
true. true means "the code was not returned, it was e-mailed to the owner".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
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.
FirstName, LastName, Company, EMail, Phone, address fields.0 for a new profile; a value above zero is the id of the one being edited.null or an empty string lets it carry on.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
Runs after the profile was saved.
Hook::add('action:domain.whois_profile_saved', 10,
function ($uid, $profileId, $information) {
Acme::indexProfile($uid, $profileId, $information['EMail'] ?? '');
});Pitfalls
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.
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.
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 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.
Related Articles
- Domain Acquisition Hooks
- Domain DNS Hooks
- Domain Hooks
Merci pour votre retour !
Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.