NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

Hook these on the host side; they carry ids and scalars only (plus the reason enum), never the model or the plaintext. ownerType is the owner’s morph class, so listeners can tell account types apart:

EventPayloadWhen
RefreshTokenIssuedtokenId, familyId, ownerType, ownerIdA token is issued — trigger async device/geo enrichment.
RefreshTokenRedeemedtokenId, familyId, ownerType, ownerIdA token was legitimately spent (rotated) — audit rotations or meter session churn.
SessionRevokedtokenId, familyId, ownerType, ownerId, reason, accessReferenceAn active row is revoked, or a session caught mid-rotation is sealed.
RefreshTokenReuseDetectedfamilyId, ownerType, ownerId, revokedCountA spent token was re-presented — to redeem or to log out — and something was revoked: a theft signal.
use Illuminate\Support\Facades\Event;
use RoundlyConsulting\RefreshTokens\Events\RefreshTokenRedeemed;
use RoundlyConsulting\RefreshTokens\Events\SessionRevoked;

Event::listen(function (RefreshTokenRedeemed $event): void {
    // $event->tokenId, $event->familyId, $event->ownerType, $event->ownerId
    // audit rotations, meter session churn, …
});

Event::listen(function (SessionRevoked $event): void {
    // $event->reason is a RevocationReason; $event->accessReference is ?string
    // write an audit-trail entry, send a "signed out" notification, …
});

All events live in RoundlyConsulting\RefreshTokens\Events. rotate() fires one RefreshTokenRedeemed and one RefreshTokenIssued; RefreshTokenIssued is not fired for a replacement that comes back already revoked.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.