# Account Restriction Hooks

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

The six hooks over blacklisting, bulk suspension, dormant accounts and bulk messages.

## Overview

The operations that narrow what an account may do live here: blacklisting, suspending or cancelling every service, and deleting an account left unused.

All of them reach wide and are hard to undo. That is what makes the gates valuable: they are the only place a wrong click can still be stopped.

## Reference

### Stopping a blacklisting

gateuser.blacklist_add

`AdminUsers` before the write

Runs before a customer is put on the blacklist.

Parameters 3

$user_idintThe customer to be blacklisted.

$reasonstringThe reason code: payment fraud, chargeback, abuse, spam, terms breach, false information or other.

$restrictionsarrayThe restrictions to apply: new orders, renewals, tickets and suspending services.

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.blacklist_add', 10, function ($user_id, $reason, $restrictions) {
    // Ask for a second approval on an enterprise account.
    if (Acme::isEnterprise($user_id)) return 'An enterprise account needs manager approval.';

    return null;
});
```

### Following a blacklist change

actionuser.blacklist_changed

`AdminUsers` three states

Runs after the blacklist state of a customer changes.

Parameters 4

$user_idintThe customer affected.

$statusstringWhat happened: `add`, `add-2` or `remove`. There are two separate adding values; a listener watching only one misses the other.

$reasonstringThe reason.

$admin_idintThe administrator who did it.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:user.blacklist_changed', 10,
    function ($user_id, $status, $reason, $admin_id) {
        // There are two separate adding values.
        if ($status !== 'remove') Acme::flagRisk($user_id, $reason);
    });
```

### Stopping a bulk service cancellation

gateuser.services_bulk_cancel

`AdminUsers` all at once

Runs before **every** service of a customer is cancelled. It is one click, and hard to undo.

Parameters 1

$user_idintThe customer whose services would be cancelled.

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.services_bulk_cancel', 10, function ($user_id) {
    // One click takes every service.
    if (Acme::hasActiveContract($user_id)) return 'An account under contract cannot be cancelled in bulk.';

    return null;
});
```

### Following a bulk suspension

actionuser.services_bulk_suspended

`AdminUsers` all at once

Runs after every service of a customer is suspended.

Parameters 1

$user_idintThe customer whose services were suspended.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:user.services_bulk_suspended', 10, function ($user_id) {
    Acme::notifyAccountFrozen($user_id);
});
```

### Stopping a dormant account being deleted

gateuser.delete_dormant

`cronjobs` per account

Runs before an account left unused for a long time is deleted. The hook fires **per candidate**: one round can call it hundreds of times.

Parameters 2

$userIdintThe account up for deletion.

$uarrayThe candidate row: name, address and how long it has been dormant.

Return 1

string|null**A non-empty text skips this account** and the reason reaches the result report. Unlike other gates nothing is thrown: the round carries on with the remaining candidates.

Listener PHP

```php
Hook::add('gate:user.delete_dormant', 10, function ($userId, $u) {
    // It runs per account: keep it light.
    if (Acme::hasRetentionHold($userId)) return 'under a retention obligation';

    return null;
});
```

### Changing the bulk message recipients

filteruser.bulk_recipients

`AdminUsers` count and list together

Runs once the recipient pool for a bulk email or text message is built.

Parameters 4

$resultarrayby linkThe recipient pool: a count and the contacts. ? **Keep the two in step**: dropping a contact without adjusting the count shows a wrong recipient total on screen.

$dbTypestringWho it goes to: customers or staff.

$notifTypestringWhich channel: email or text message.

$filtersarrayThe filters applied.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:user.bulk_recipients', 10,
    function (&$result, $dbType, $notifType, $filters) {
        // The COUNT and the LIST must stay in step.
        $result['contacts'] = Acme::dropOptedOut($result['contacts'] ?? []);
        $result['count']    = count($result['contacts']);
    });
```

## Pitfalls

> **Blacklisting has two separate adding values**
> 
> The status field carries three values and **two of them mean adding**. A listener watching only one misses a customer blacklisted through the other path. Testing for removal and treating the rest as adding is safer.

> **Update the recipient list and the count together**
> 
> The bulk recipient filter carries both the contacts and the count. Dropping a contact and leaving the count alone shows the administrator a **wrong recipient total** and skews the decision to send.

## 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)
