Customer Account and Contact Hooks
The fourteen hooks over the customer account and site contact: API keys, the profile, currency, the contact form and the newsletter.
Overview
The traces a customer or visitor leaves on the site live here: API keys, the profile, the currency preference, the contact form and the newsletter.
In most of these hooks the data comes from an unverified source: the visitor filling in the contact form, the address signing up, the bot requesting a page that does not exist. Check it yourself before carrying it to an outside service.
Reference
Following API key permissions changing
Runs after the permissions of an API key change. A widening of permissions is a security event.
Hook::add('action:client.api_key_updated', 10, function ($owner_id, $id, $perms) {
// The old set is not passed: keep it yourself to compare.
Acme::recordScopes($id, $perms);
});Following an API key being regenerated
Runs after the value of an API key is regenerated. The record stays, the credential changes: calls made with the old value no longer pass.
Hook::add('action:client.api_key_regenerated', 10, function ($owner_id, $id) {
// The old value no longer passes: refresh your own cache.
Acme::invalidateCachedKey($id);
});Following an API key being deleted
Runs after an API key is deleted.
Hook::add('action:client.api_key_deleted', 10, function ($owner_id, $id) {
Acme::revokeIntegration($id);
});Following a customer profile being updated
Runs after a customer profile is updated.
Hook::add('action:client.profile_updated', 10, function ($uid, $data_updates, $info_updates) {
// $uid is the ACCOUNT id, not the login.
Acme::syncCrm($uid, $data_updates + $info_updates);
});Following a currency change
Runs when a visitor changes the site currency. The change is already applied.
Hook::add('action:client.currency_changed', 10, function ($new_cid, $old_cid, $member) {
// For a guest the member data arrives EMPTY.
if (!$member) return;
Acme::rememberCurrency((int) ($member['id'] ?? 0), $new_cid);
});Following a page that was not found
Runs when a requested address does not resolve. It is the most direct way to find broken links.
Hook::add('action:client.page_not_found', 10, function ($url) {
// Bot traffic can outnumber human traffic: filter it.
if (Acme::looksLikeScanner($url)) return;
Acme::noteBrokenLink($url);
});Following a contact form submission
Runs after a visitor submits the contact form.
Hook::add('action:client.contact_submitted', 10,
function ($message_id, $full_name, $email, $phone, $message, $ip) {
// All of these come FROM A VISITOR and are unverified.
Acme::pushToCrm($message_id, $email, $message);
});Following a contact message being answered
Runs after an administrator answers a contact message.
Hook::add('action:client.contact_message_replied', 10, function ($message, $admin_message) {
Acme::closeCrmCase((int) ($message['id'] ?? 0));
});Following a message being reported as spam
Runs after a contact message is reported as spam. The two flags beside it say how far the action went.
Hook::add('action:client.contact_message_spam_reported', 10,
function ($message, $block_emails, $report_spam) {
// The two flags are independent.
if ($report_spam) Acme::shareBadAddress($message['ip'] ?? '');
});Stopping a message becoming a ticket
Runs before a contact message is turned into a support ticket.
Hook::add('gate:client.contact_message_to_ticket', 10,
function ($message, $department, $staff) {
if (Acme::blockedSender($message['email'] ?? '')) return 'No ticket can be opened for this sender.';
return null;
});Changing the contact page data
Runs before the contact page is shown. The offices and support hours are already resolved for the active language.
Hook::add('filter:client.contact_page_data', 10, function (&$page_data, &$lang) {
// A key you add becomes a template variable.
$page_data['acme_map'] = Acme::mapEmbed($lang);
});Stopping a newsletter sign-up
Runs before an address joins the newsletter list. The place to keep disposable addresses out.
Hook::add('gate:client.newsletter_subscribe', 10, function ($email, $lang) {
if (Acme::disposableDomain($email)) return 'This address is not accepted.';
return null;
});Following a newsletter sign-up
Runs after an address joins the newsletter list.
Hook::add('action:client.newsletter_subscribed', 10, function ($email, $lang, $added) {
// The language decides the content language of bulk mailings.
Acme::syncMailingList($email, $lang);
});Following a newsletter unsubscribe
Runs after an address leaves the list. It happens through the link in the email, with no sign-in needed.
Hook::add('action:client.newsletter_unsubscribed', 10, function ($email) {
// Remove it from your own list too, or mailings carry on.
Acme::dropFromMailingList($email);
});Pitfalls
The id handed to the update hook belongs to the account being changed, not to whoever made the change. A sub-user with the right permission can update another account’s profile. If you record "who did it", read it from the session separately.
Scanning bots try addresses that do not exist all day, and on a site those requests easily outnumber human ones. A listener writing a record on every call inflates its own table and buries the real broken links. Put a filter in front of it.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.