Domain Lifecycle End Hooks
The eight hooks along an unpaid domain's road to the end: the grace period, redemption, the drop, and importing.
Overview
A domain past its date does not vanish in one step. First comes the grace period, where the name still renews at the normal price. Then redemption: the name stops working and getting it back is far more expensive. Finally the drop, where the name opens to everyone.
Each of the three has its own hook and all of them run from a scheduled task. Nobody presses a button and no one is watching a screen.
Reference
Catching the start of the grace period
Runs the moment the domain entered its grace period; the name still works and renews at the normal price.
stage, days_past_due, days_in_stage. This stage also carries grace_days, grace_fee and grace_fee_active.Hook::add('action:domain.grace_started', 10, function ($service_id, $state) {
// Reaching the customer at this stage is the cheapest way back.
Crm::nudge($service_id, 'domain-grace', (int) ($state['grace_days'] ?? 0));
});Catching the start of redemption
Runs where the grace period ended. The name has stopped working here and a redemption fee applies.
stage, days_past_due, days_in_stage. This stage carries redemption_days, redemption_fee, amount_cid and can_restore.Hook::add('action:domain.redemption_started', 10, function ($service_id, $state) {
// The fee and its currency arrive in the state; do not work them out.
if (!empty($state['can_restore']))
Crm::urgent($service_id, (float) ($state['redemption_fee'] ?? 0),
(int) ($state['amount_cid'] ?? 0));
});Stopping the drop request
Runs before the cancellation request reaches the provider. It sits inside a scheduled task, so stopping it means nobody sees an error.
stage, days_past_due, days_in_stage. Hook::add('gate:domain.delete_request', 10, function ($service, $state, $module) {
// Hold the drop a round where the customer promised to pay.
if (Acme::promiseToPay((int) ($service['id'] ?? 0)))
return 'A payment was promised; the drop was held.';
return null;
});Following the drop request
Runs after the cancellation request was sent to the provider.
stage, days_past_due, days_in_stage. Hook::add('action:domain.delete_requested', 10,
function ($service, $state, $module) {
Audit::note('domain-drop', $service['name'] ?? '', $module ?: 'no module');
});Catching the finished drop
Runs after the service was cancelled and the domain dropped. This is the last step.
stage, days_past_due, days_in_stage. At this point stage is the drop stage.Hook::add('action:domain.purged', 10, function ($service_id, $state, $module) {
// The name is no longer ours: clear what hangs off it.
DnsMonitor::forget($service_id);
});Following an expiry notice
Runs after a notice was sent for a domain past its date.
Hook::add('action:domain.expired_notice_sent', 10,
function ($service_id, $delayed_day) {
// It runs per milestone: day 1, day 7 and day 15 arrive separately.
if ($delayed_day >= 15) Ops::escalate('domain-expiry', $service_id);
});Following an import
Runs after domains were imported from a provider. The hook runs once for the whole batch, not per name.
"example.com (#123)". Labels arrive, not service records, so the id wants picking out of the text.Hook::add('action:domain.imported', 10, function ($imported, $module_name) {
// Runs ONCE for the whole batch, not per name.
Ops::note('domain-import', $module_name . ': ' . count($imported));
});Changing the grace and redemption fee
Runs before a grace or redemption fee is saved. This is how you hold one fee policy in one place instead of typing it extension by extension.
amount, cid (currency), owner_id, status and the rest.grace or redemption.Hook::add('filter:domain.grace_redemption_save', 10, function (&$set_data, $type, $id) {
// Make the grace period free on every extension.
if ($type === 'grace') $set_data['amount'] = 0;
});Pitfalls
Every hook here runs from a scheduled task. Stopping a gate means nobody sees an error: not the customer, not the operator. Record your decision on your own side, or no one can tell why the domain never dropped.
The task tries again next round. Thinking "I stopped it once" at the gate is wrong; your condition is asked afresh every round. For a lasting hold, keep your own mark and read it at the gate.
Expiry notices go out milestone by milestone and the hook runs again at each. A listener reacting without reading the day value produces a run of alerts for a single name.
The import hook hands you strings shaped like "example.com (#123)". Expecting service records is wrong; where you need an id, pick it out of the text and read the record yourself.
Related Articles
- Domain Acquisition Hooks
- Scheduled Task Hooks
- Domain Hooks
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.