# Laravel exception notifications on your phone

> Turn Laravel exceptions into one push per failure with Honk::reportable(), throttled per exception, plus scheduler alerts and Honk::fake() for tests.

Source: https://honk-me.app/guides/laravel-exception-notifications

Laravel already reports exceptions: to your log and, with a log channel like Slack, to a chat. The trouble starts when the same exception fires 300 times in a minute. You want to hear about it once, with enough detail to act, and then be left alone until something new breaks.

This guide sets that up with the `honk-me/honk-me` package and Laravel 13. Exceptions become pushes, repeats of the same failure fold into one group, and scheduled tasks tell you when they fail and when they work again.

## Before you start

- Laravel 13 on PHP 8.3 or later.
- A Honk account (invite-only for now: [request access](https://honk-me.app/request-access)) and a project with an ingestion key: in the web app, open the project, then **Keys**.

## 1. Install the package

```sh
composer require honk-me/honk-me
# publishes config/honk.php and adds HONK_URL and HONK_KEY to .env.example
php artisan honk:install
```

Put the server URL and the key in `.env`. Never commit the key; `.env.example` only gets empty placeholders.

```dotenv
HONK_URL=https://honk-me.app
HONK_KEY=honk_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Check the connection. `honk:test` sends a Light honk and prints the message ID:

```sh
php artisan honk:test         # sends a light honk and prints the message id
php artisan about --only=honk # URL, key prefix (never the secret), queue, Context metadata
```

## 2. Report exceptions

One line in `bootstrap/app.php`:

```php
// bootstrap/app.php
use HonkMe\Laravel\Facades\Honk;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(Honk::reportable());
})
```

From now on, every reported exception becomes a Long honk, sent as a *problem*. Three details keep things calm:

- **One group per place in the code.** The group key is `exceptions/<Class>@<file>:<line>`, so 300 identical exceptions are one incident with a count, and a different exception is a different incident.
- **At most one honk per group every 5 minutes.** The throttle lives in your cache, so the 299 repeats don’t even leave your server.
- **Sent after the response.** The default mode, `defer`, sends once the HTTP response has gone out, so a slow network never slows down a page.

It does nothing while `HONK_URL` or `HONK_KEY` is missing, which keeps local development and CI quiet, and it never throws.

You can pick a different level or throttle window per app, for example a Blast with a one-minute throttle for a payment service:

```php
$exceptions->report(Honk::reportable(severity: 'blast', throttleSeconds: 60));
```

Everything else lives in `config/honk.php`. Skip exceptions you expect, like a declined payment the UI already explains, and choose how they’re sent:

```php
// config/honk.php (published by php artisan honk:install)
'exceptions' => [
    'enabled' => (bool) env('HONK_EXCEPTIONS', true),
    'severity' => 'long',
    'channel' => 'exceptions',
    'throttle' => 300,          // seconds per group
    'mode' => 'defer',          // defer (after the response), sync or queue
    'ignore' => [
        \App\Exceptions\PaymentDeclined::class,   // expected, already handled in the UI
    ],
],
```

Exception messages are sent as is, truncated to fit. Keep personal data out of them, or it ends up in a push.

## 3. Now, after the response, or from the queue

The same choice exists for everything you send yourself:

```php
use HonkMe\Laravel\Facades\Honk;
use HonkMe\Message;

Honk::defer()->beep('New order', "{$order->email} paid {$order->total} €");   // after the response
Honk::queue(Message::make('Imported 1 204 rows')->beep()->channel('imports'));   // a queued job
Honk::loud('Disk 91%', '/var on app-01', ['groupKey' => 'disk/app-01/var']);     // right now
```

- `Honk::defer()` runs in the same process after the response, so you don’t need a worker. If the process dies before it runs, that honk is lost; for most alerts, that’s fine.
- `Honk::queue()` runs in a queue worker with retries and backoff, and survives restarts. Use it for anything that must arrive.
- The plain helpers send right away, with retries, which suits commands, jobs and scheduled tasks.

Both `defer()` and `queue()` validate the message immediately, so a bad field fails in your code, not later in a worker.

## 4. Scheduled tasks

```php
// routes/console.php
Schedule::command('backup:run')->daily()->honkOnFailure();          // problem on failure, recovery once it works again
Schedule::command('reports:send')->hourly()->honkOnSuccess();       // a beep after every successful run
```

`honkOnFailure()` sends a problem with the exit code and the end of the output, grouped per task. The next time the task succeeds, it sends a recovery, but only after a failure, so a healthy task never pings you. `honkOnSuccess()` sends a Beep-beep after every successful run, for the reports you actually want to hear about.

## 5. Test it

`Honk::fake()` records what would be sent or queued and never touches the network. It validates like the real client, so an invalid message still fails the test:

```php
use HonkMe\Laravel\Facades\Honk;
use HonkMe\Message;
use HonkMe\Severity;

Honk::fake();

$this->post(route('quote.store'), $data);

Honk::assertSent(fn (Message $m) => $m->groupKey === 'requests/1' && $m->severity === Severity::Light);
Honk::assertSentTimes(1);
Honk::assertNotSent(fn (Message $m) => $m->severity === Severity::Blast);
Honk::assertQueued(fn (Message $m, string $idempotencyKey) => $m->channel === 'imports');
Honk::assertNothingSent(); // also assertNothingQueued(), assertNothingOutgoing()
```

## What Honk adds to Laravel’s reporting

Your log still gets every exception. Honk adds one push per new failure on your iPhone, Apple Watch or browser, folds the repeats into a count, and keeps them in one inbox with your cron jobs and servers. To handle some of them differently without touching code, add a rule to the project: for example, send `exceptions/` from a noisy integration to the digest.
