Skip to main content
The OnReceiveEvents handler is a custom module hook that allows you to intercept and process events received from supported message providers before they are delivered to GraphQL subscription clients. This handler is called whenever a batch of events is received from a provider, giving you the opportunity to filter, transform, enrich, or validate events before they reach your subscribers. This handler is particularly useful for:
  • Event filtering: Remove unwanted events based on custom logic
  • Data transformation: Modify event payloads to match client expectations
  • Event enrichment: Add additional data to events from external sources
  • Authentication and authorization: Filter events based on user permissions
  • Monitoring and analytics: Log or track events for observability
If there is no active subscription this handler is not executed, even if new messages arrive at the provider. This is because the Router will not listen for messages on the provider topic/queue until at least one client subscribes to a particular subscription.

Handler Interface

In order to use the OnReceiveEvents handler you need to create a Custom Module which implements the StreamReceiveEventHandler interface.

Asynchronous Execution and Performance Considerations

The OnReceiveEvents handler is executed asynchronously for each active subscription when events are received from the provider. To control resource usage and prevent overwhelming your system, you can configure the maximum number of concurrent handlers using the max_concurrent_handlers configuration option.
This limit applies per Trigger, not globablly. When the maximum number of concurrent handlers for a topic is reached, the router will not poll new events from the message queue until a handler finishes and becomes available again. To avoid delivering events out of order to subscription clients, the router waits for all handlers to complete before polling the next batch of events. This waiting period is configurable via the router settings:
If the timeout is reached, the router immediately polls the next batch of events, which may result in out-of-order delivery to subscription clients. In this case, a warning is logged. It is recommended to use ctx.Context(), which is cancelled in such situations. You can use this context to abort any long-running operations:
The router does not abort the handler when the context is cancelled. Instead, it proceeds to receive the next batch of events from the provider. In this case, events may be delivered out of order because long-running handlers are still processing the previous batch.
While the handler limit is reached, the router will not poll the next batch of events from the provider. This effectively means that the subscription will not receive updates until a handler becomes free again.

Error Handling

When the OnReceiveEvents handler returns an error, the router takes the following actions:
  1. Send Returned Events: Alongside the error you can return events, if you wish to sent them to the client prior connection closing
  2. Subscription Closure: The affected subscription is immediately closed for the client that encountered the error
  3. Error Logging: The error is logged by the router with details about the subscription, provider, and field name
  4. Error Deduplication: If multiple subscriptions experience the same error for the same events, the router deduplicates the error messages in the logs to prevent spam
  5. No Error Propagation: The error is not sent directly to the GraphQL client - the subscription simply closes
Returning an error from OnReceiveEvents will close the subscription for that specific client. Use this only when you want to terminate the subscription due to unrecoverable conditions. For filtering events, return an empty event list instead of an error.
The error gets logged by the router but it won’t be send to the client. From the view of the client the subscription closes server-side without a reason. We are working on a solution for this.
Example of proper error handling:

Usage Example

Complete Custom Module with Event Bypass

The following example contains a complete Custom Module implementation, including handler registration, with a handler that will simply bypass events unchanged. This is not useful on it’s own but demonstrates how to register your OnReceiveEvents Custom Module.

Restrict Handler to run on certain subscriptions and providers

Most of the time you want your hook to only deal with a certain subscription.
The OnReceiveEvents Handler is run for every subscription configured for Cosmo Streams. You can access the name of the subscription you care for and return early if it’s not the right one.

Filter out events based on clients authentication token claim

You can use ctx.Authentication() to access authentication data, such as tokens, if available. Based on that you can filter events, if the token misses the proper claim.

Filter out events based on message metadata

Certain providers enrich their messages with metadata accessible by the Router. Kafka and NATS, for example, have the option to add headers to messages. Here’s an example that filters out all messages coming from a Kafka instance where a header indicates it’s not meant for GraphQL subscriptions.

Transform events

You can change event data. For example you could change the id of the entity you want to resolve for a client.
Or if your schema defines a union as the return type of a subscription, you can choose to resolve a different entity
This will lead the router to fetch and resolve the Cat entity with id 3, no matter what the event originally references.