Docs / Laravel package

Laravel package

stickysoftware/bump-laravel sends events from a Laravel app and reads totals back. It gives every event an id, sends them in batches once per request or job, and retries when Bump can't be reached. This page covers what most apps need. The README has the rest.

Install

It needs PHP 8.3 or later and Laravel 12.45 or later, or 13.

composer require stickysoftware/bump-laravel

Add both tokens to .env, each where it belongs:

BUMP_LIVE_TOKEN=bump_live_…
BUMP_TEST_TOKEN=bump_test_…

The live token is used only when APP_ENV is exactly production. Every other environment uses the test token, so local, staging and CI never touch live numbers. Without the token for the current environment, calls are dropped with a log line. Under APP_ENV=testing nothing is sent, unless you set BUMP_ENABLED=true.

Delivery

Events go out from a queued job, so the app needs a running queue worker. Without one, send them over HTTP when the request or command ends:

BUMP_DELIVERY=sync

Tracking

use StickySoftware\Bump\Facades\Bump;

Bump::track('orders', identifiable: $order->user, amount: $order->total);
Bump::track('members', identifiable: $team);
Bump::track('members', identifiable: $team, count: -1);

Only the trackable is required. The other arguments are identifiable, count, amount, occurredAt, note, activity and id, and what you leave out gets Bump's defaults. Calls from queued jobs and console commands send activity: false, since the system acted there, not a person.

adjust() is for corrections. It sends count: 0 and activity: false unless you pass them, so a refund changes the amount without adding an order or an active user:

Bump::adjust('orders', identifiable: $order->user, amount: -25.00, note: 'partial refund');

Models as identifiables

Pass a model and the package sends a handle for it: its bumpHandle() when it has one, otherwise its prefixed ID from spatie/laravel-prefixed-ids (usr_3f9a…), otherwise its morph alias or class name plus its key (user_12). No label is sent unless the model has a bumpLabel(). Settle on the handles before the first live event: a new handle is a new identifiable, starting at zero.

Event ids

Without id:, the package makes up an id per call, which keeps its own retries from counting twice. Pass your own when the same thing can happen twice in your app, like a job that runs again:

Bump::track('orders', identifiable: $order->user, amount: $order->total, id: "order-{$order->id}");
Bump::adjust('orders', identifiable: $order->user, amount: -25.00, id: "refund-{$refund->id}");

One id per event. A refund sent as order-1042 would be a duplicate of the order, skipped and listed in Developers > Problems as a reused id.

Forgetting

Bump::forget($user);

It takes one model or handle, and a token made with Can forget identifiables on. Without that, nothing is forgotten and the package logs a warning. Forgetting an identifiable has what Bump does with it.

Reading totals

$orders = Bump::total('orders');

$orders->count;  // 1042
$orders->amount; // "52099.58"

It takes a token made with Can read totals on. Sending never throws, but a read does: a failed one throws a BumpException with the API's error code. Read on a schedule, store the number, and show the stored one, so a page never waits on Bump. The README has a scheduled command that does this, and Reading totals has the API side.

Backfill

php artisan make:bump-backfill OrdersBackfill
# fill in app/Bump/OrdersBackfill.php: your model, its date column, an id per row
# copy Tracking since from Developers > Events, in live
php artisan bump:backfill "App\Bump\OrdersBackfill" --until="2026-10-16T14:02:11+02:00" --dry-run
php artisan bump:backfill "App\Bump\OrdersBackfill" --until="2026-10-16T14:02:11+02:00"

--until ends the import where live counting began, so nothing counts twice. The events go out marked as backfill, so the milestones they reach never go to webhooks.

Testing

Bump::fake() records calls and sends nothing:

use StickySoftware\Bump\Facades\Bump;
use StickySoftware\Bump\Testing\TrackedEvent;

test('paying an order tracks it', function () {
    Bump::fake();
    $order = Order::factory()->create();

    $this->post("/orders/{$order->id}/pay");

    Bump::assertTracked('orders', fn (TrackedEvent $event) => $event->hasIdentifiable($order->user));
});

For code that reads totals, Bump::fake()->withTotal('orders', count: 3, amount: 59.94) sets what total() returns.