Symfony's RemoteEvent component is mostly known through usage with the Webhook component. That is a great way to use it, but it is not the only one.

You can also use RemoteEvent with a different transport layer, for example through Messenger using a queue shared between applications, and skip webhook delivery completely.

Pauline, Piet, and their way of communicating

We have two cats at home: Pauline and Piet.

They communicate in their own ways. They do not use the same sounds, but they are similarly enough to understand each other. They also have different preferences when it comes to food:

Pauline is Team Gelee.
Piet is Team Sauce.

Different preferences, different "internal models". Still, when food is about to happen, they always manage to alert each other.

In this article:

Pauline is the producer of events (the one who smells the food first)
Piet is the consumer of events (the one who reacts to the call and runs to the feeding place).

Let's implement their communication in code.

The idea in a nutshell

Pauline as the producer dispatches a ConsumeRemoteEventMessage on the Messenger bus.
On the consumer side, Piet handles it via a #[AsRemoteEventConsumer].

Prerequisites

To allow our cat communication between two symfony applications, we need the following components:

composer require symfony/remote-event symfony/messenger symfony/uid

We also need a messenger transport, for this demo we use amqp-messenger. In a real world scenario, we would most likely use a transport that supports fifo with a group-id like nats or aws sqs.

composer require symfony/amqp-messenger

Producer side (Pauline)

Pauline publishes an event named cat.smelled_food, identified by a UUID, and with a payload containing the smell of the food.

use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\RemoteEvent\Messenger\ConsumeRemoteEventMessage;
use Symfony\Component\RemoteEvent\RemoteEvent;
use Symfony\Component\Uid\Uuid;

$event = new RemoteEvent('cat.smelled_food', Uuid::v4()->toRfc4122(), [
    'smell' => 'chicken',
    'cat' => 'Pauline',
]);

$bus->dispatch(new ConsumeRemoteEventMessage('cat_language', $event));

Why ConsumeRemoteEventMessage?

When used with Symfony Webhook, this message is usually created at the receiving webhook endpoint and dispatched for async handling, to allow the webhook controller to return a response immediately.

Here we use it differently: We create and dispatch it in the producing service directly and send it to the shared queue, without any webhook handling in between. Same message contract, different entrypoint. But it is consumed in the same way as with webhooks, so we can reuse the same consumer pipeline on the receiving side.

Consumer side (Piet)

On the receiving side Piet registered a remote-event consumer for cat_language. When Piet receives cat.smelled_food, he runs to the feeding place.

use App\Domain\Cat;
use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface;
use Symfony\Component\RemoteEvent\RemoteEvent;

#[AsRemoteEventConsumer('cat_language')]
final class CatLanguageConsumer implements ConsumerInterface
{
    public function consume(RemoteEvent $event): void
    {
        if ('cat.smelled_food' !== $event->getName()) {
            return;
        }

        $smell = (string) ($event->getPayload()['smell'] ?? 'unknown smell');

        $this->cat->runToFeedingPlace($smell);
    }
}

Why JSON serialization matters (Gelee vs Sauce)

Pauline and Piet do not share the same food format. Imagine two Symfony applications with different classes.

If both sides use their own models, direct PHP serialization is a bad contract. JSON gives us a neutral format both sides can understand and interpret in their own way. In this example it's just a string, but it could be a more complex structure.

We will need serializer-pack, which bundles the serializer component with others necessities like property-info.

composer require symfony/serializer-pack

Producer Messenger config (JSON)

framework:
    messenger:
        serializer:
            default_serializer: messenger.transport.symfony_serializer
            symfony_serializer:
                format: json

        transports:
            family_cats: 'amqp://guest:guest@localhost:5673/%2f/family_cats'

        routing:
            'Symfony\Component\RemoteEvent\Messenger\ConsumeRemoteEventMessage': family_cats

Consumer Messenger config (JSON)

framework:
    messenger:
        transports:
            family_cats:
                dsn: 'amqp://guest:guest@localhost:5673/%2f/family_cats'
                serializer: messenger.transport.symfony_serializer

To consume events, run:

bin/console messenger:consume family_cats  

When order suddenly matters (and why FIFO helps)

Now the cat drama.

Pauline sends:

cat.smelled_food (food is there)
cat.moved_outside (she leaves)

If Piet receives these out of order, he might:

Arrive when there is no food anymore
Or stay inside while Pauline already left

For these cases, use FIFO ordering with a message group key. How you achieve this depends on the transport. Some brokers support FIFO natively (e.g. via message group IDs), others require single-consumer queues or partitioning.

The key idea: group related events by a stable key (e.g. the cat name) and ensure messages within a group are delivered in order.

The practical effect:

All Pauline events stay ordered in one stream
Piet can still process other cats in parallel, so he can meet his friends while Pauline is sleeping

Each type (cat_language) can only have one consumer. That should not lead to us putting all reactions for different events into one class. Instead we could dispatch the RemoteEvent itself into Messenger (sync), and use MessageHandlers that filter by $event->name.

Summary

RemoteEvent is often introduced via Webhook examples. But the component also works well with Messenger as transport.

If you dispatch ConsumeRemoteEventMessage directly, keep payloads JSON, and add FIFO grouping when needed, you get a very practical state-transfer setup between independent Symfony applications.