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

Every event is a final readonly class in RoundlyConsulting\Tickets\Events implementing ShouldDispatchAfterCommit: it fires only once the change committed, never for a rollback. $actor is who acted (null for the system). Every event about one ticket — 33 of the 34, all but GuestConverted — implements Contracts\TicketEvent:

interface TicketEvent
{
    public function ticket(): Ticket;   // TicketMerged: the source, the ticket merged away
    public function actor(): ?Model;    // null for the system, and always for TicketOverdue,
                                        // TicketDueSoon (the sweep) and TicketMentioned
}

Laravel’s dispatcher delivers an event to the listeners of the interfaces it implements, so one typed listener hears them all — an audit log, a webhook relay or a dashboard feed:

use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Tickets\Contracts\TicketEvent;

Event::listen(TicketEvent::class, function (TicketEvent $event): void {
    AuditLog::create([
        'ticket' => $event->ticket()->reference,
        'event' => class_basename($event),          // TicketReplied, TicketAssigned, …
        'actor_type' => $event->actor()?->getMorphClass(),
        'actor_id' => $event->actor()?->getKey(),
    ]);
});

Before sending anything outward, mind the agent-only events: TicketNoteAdded, and a TicketMentioned or TicketCommentUpdated / TicketCommentDeleted about a note or a held reply, carry content no requester may see.

The events

EventPayloadFires when
TicketCreatedticket, actorA ticket was filed.
TicketUpdatedticket, changed, actorSubject, description or the host metadata changed (changed holds the keys).
TicketPriorityChangedticket, from, to, actorThe priority changed (not when set to the same value).
TicketCategoryChangedticket, from, to, actorcategorize() moved the ticket — also for each ticket a category delete reassigns.
TicketDueDateChangedticket, from, to, actorSet, moved or cleared.
TicketRequesterChangedticket, requester, actorAlso on a guest conversion.
TicketDeleted / TicketRestoredticket, actorSoft delete / restore.
TicketStatusChangedticket, transition, from, to, actor, systemEvery state change but the initial one (rollbacks and adoptions included); from / to are lifecycle’s raw values.
TicketResolved / TicketClosedticket, actorEnters a Resolved / Closed state.
TicketReopenedticket, actorLeaves Resolved or Closed for Open, Active or Waiting.
TicketRepliedticket, comment, role, actorA published public reply (not a held one).
TicketNoteAddedticket, comment, actorAn internal note.
TicketCommentUpdated / TicketCommentDeletedticket, comment, actorA message was edited / deleted — a reply, a held reply or a note: check the comment before showing it.
TicketFirstRespondedticket, comment, actorThe first public agent reply.
TicketMentionedticket, comment, mentionedA mention, only for someone who may read the comment.
TicketAssignedticket, assignee, actorAssigned or claimed (not for the same assignee again).
TicketUnassignedticket, assignee, actorThe assignee was removed (assignee is the previous one).
TicketWatcherAdded / TicketWatcherRemovedticket, watcher, actorExplicit adds and removals (auto-watches are silent).
TicketTagged / TicketUntaggedticket, tags, actorTags actually added / removed (names) — TicketUntagged also for each live ticket a tag delete takes the tag off.
TicketLinked / TicketUnlinkedticket, related, type, actorA link was added / removed (a duplicate add fires nothing; a merge’s duplicates link fires none).
TicketMergedsource, target, result, actorA merge committed; ticket() is the source.
TicketAttachmentAdded / TicketAttachmentRemovedticket, media, actorTicket-level files (message files are comments’).
TicketFieldsChangedticket, keys, actorCustom field values set or forgotten — keys, never values.
TicketOverdue / TicketDueSoonticket, dueAtThe sweep flagged it, once per due date.
GuestConvertedguest, user, tickets, actorA guest became a registered user (tickets = the count moved); not a TicketEvent.
GuestAccessRevokedticket, version, actorEvery guest link voided — explicitly, on merge, or on close with revoke_on_close.
  • An internal note also reaches comments-for-laravel’s own events (CommentCreated, CommentMentioned), which fire for every comment — check meta.tickets.internal before telling anyone.
  • Lifecycle’s LifecycleTransitioning / LifecycleTransitioned fire for tickets too; prefer the ticket events above.
  • Tickets::fake() does not fake events — every call still runs — so Event::fake() works next to it.

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.