# Account and Contact Hooks

https://dev.wisecp.com/es/account-contact-hooks

The eight hooks over the profile picture, addresses, linked providers and preferences.

## Overview

Everything a customer uses to describe themselves lives here: the profile picture, billing addresses, linked social accounts and notification preferences.

The address side carries four hooks: the save gate and its event, the default changing, and deletion. The default address is the one printed on invoices.

## Reference

### Following a profile picture change

actionuser.avatar_changed

`AccountUsers` two branches

Runs when a customer uploads or removes a profile picture.

Parameters 3

$uidintThe account owner.

$actionstringWhat happened: `set` uploaded, `removed` taken away.

$picturestringThe path of the new picture. On the removal branch it is **always empty**: check the action before using the path.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:user.avatar_changed', 10, function ($uid, $action, $picture) {
    // On the removal branch the path arrives EMPTY.
    if ($action === 'removed') { Acme::dropAvatar($uid); return; }

    Acme::mirrorAvatar($uid, $picture);
});
```

### Stopping a contact being saved

gateuser.contact_save

`AccountUsers` zero means new

Runs before a customer saves a contact address.

Parameters 3

$uidintThe account the address will belong to.

$dataarrayThe address data: name, email, phone, country and tax details.

$idintThe id being edited; a **zero means a new one** is being added.

Return 1

string|null**A non-empty text blocks the operation** and is shown as the error. An empty return lets it carry on.

Listener PHP

```php
Hook::add('gate:user.contact_save', 10, function ($uid, $data, $id) {
    // A zero id means a new record.
    if (!Acme::taxNumberValid($data)) return 'The tax details did not verify.';

    return null;
});
```

### Following a contact being saved

actionuser.contact_saved

`AccountUsers` create and edit

Runs after a contact address is saved.

Parameters 4

$uidintThe owner of the address.

$saved_idintThe id of the saved address.

$dataarrayThe address data.

$is_newboolTrue when it was newly added.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:user.contact_saved', 10, function ($uid, $saved_id, $data, $is_new) {
    Acme::syncContact($uid, $saved_id, $data);
});
```

### Following the default contact changing

actionuser.contact_default_changed

`AccountUsers` default changed

Runs when an address becomes the default. The default is the address printed on invoices.

Parameters 2

$uidintThe owner of the address.

$idintThe address made default.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:user.contact_default_changed', 10, function ($uid, $id) {
    // The default address is the one printed on invoices.
    Acme::syncBillingAddress($uid, $id);
});
```

### Following a contact being deleted

actionuser.contact_deleted

`AccountUsers` after deletion

Runs after a contact address is deleted.

Parameters 3

$uidintThe owner of the address.

$idintThe id deleted; the row is gone.

$existingarrayThe full address as read immediately before deletion.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:user.contact_deleted', 10, function ($uid, $id, $existing) {
    Acme::dropContact($uid, $id);
});
```

### Following a social account being linked

actionuser.connected_provider

`SocialAuth` account linking

Runs when an account is linked to an outside provider.

Parameters 2

$userobjectThe matched user record.

$mod_namestringThe name of the provider linked.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:user.connected_provider', 10, function ($user, $mod_name) {
    Acme::noteLink((int) ($user->id ?? 0), $mod_name);
});
```

### Following notification preferences changing

actionuser.notification_preferences_changed

`AccountUsers` per channel

Runs when a customer changes which notifications they receive.

Parameters 2

$uidintThe account whose preferences changed.

$prefsarrayWhat was written, **per channel**. The keys are deliberately the database column names, so which layer changed is never in doubt.

Return 1

voidThe return is ignored. The preferences are already written.

Listener PHP

```php
Hook::add('action:user.notification_preferences_changed', 10, function ($uid, $prefs) {
    Acme::syncPreferences($uid, $prefs);
});
```

### Producing the text of an activity record

filteruser.action_text

`User::addAction` only when empty

Runs while the visible text of an account activity is resolved. Produce the text for your own activity keys here.

Parameters 2

$textstringThe text the core found. The hook runs **only when no text was found**, so this is always empty. It is passed for signature consistency.

$ctxarrayContext: the activity key, the language asked for and the placeholder values.

Return 1

string|nullReturn the text you produced. Return nothing for keys that are not yours.

Listener PHP

```php
Hook::add('filter:user.action_text', 10, function ($text, $ctx) {
    // The hook runs only when no text was found.
    if (!str_starts_with($ctx['key'] ?? '', 'acme.')) return null;

    return Acme::actionText($ctx['key'], $ctx['lang'] ?? '', $ctx['variables'] ?? []);
});
```

## Pitfalls

> **The picture path is empty on the removal branch**
> 
> The profile picture hook fires for uploads and removals alike, and on removal the path field **always arrives empty**. A listener using the path directly then works with nothing on that branch.

> **The default address is the one on the invoice**
> 
> Changing the default is more than a preference: the invoices that follow carry that address. If you keep an accounting side in step, listen on this hook too.

## Related Articles

- [Customer Account Hooks](https://dev.wisecp.com/en/customer-account-hooks)
- [Invoice Lifecycle Hooks](https://dev.wisecp.com/en/invoice-lifecycle-hooks)
- [How Hooks Work](https://dev.wisecp.com/en/how-hooks-work)
