Skip to main content

Push Events

Liquid provides a comprehensive push events system that publishes real-time events to either Redis Pub/Sub or RabbitMQ message queues. This allows you to build reactive features and integrate with external services based on user actions and system events.

Overview​

When enabled, Liquid publishes events to your configured message queue whenever significant actions occur in the system. These events can be consumed by your applications to trigger notifications, analytics, webhooks, or other reactive behaviors.

Configuration​

Enabling Push Events​

To enable push events, you need to configure the following options in your app-config.service.json:

{
"privilege.can-use-push-events": true,
"system.queue-adapter": "redis",
"system.push-events": [
"user.follow",
"user.unfollow",
"user.follow-request",
"user.create",
"user.login",
"user.logout",
"user.logout-all",
"email.send"
]
}

Queue Adapters​

Liquid supports two message queue adapters:

Redis Pub/Sub​

{
"system.queue-adapter": "redis",
"privilege.can-use-cache": true,
"redis.host": "127.0.0.1",
"redis.port": 6379,
"redis.channel-name": "liquid"
}

RabbitMQ​

{
"system.queue-adapter": "rabbitmq",
"privilege.can-use-rabbitmq": true,
"rabbitmq.connectionString": "amqp://localhost:5672",
"rabbitmq.channel-name": "liquid"
}

Webhook​

{
"system.queue-adapter": "webhook",
"privilege.can-use-webhook-pusher": true,
"pusher.webhook.url": "https://api.your-app.com/liquid-events",
"pusher.webhook.secret": "your_secure_secret"
}

Backend Configuration​

Redis Configuration Options​

All Redis configuration options for Liquid:

{
"privilege.can-use-cache": true,
"redis.host": "127.0.0.1",
"redis.port": 6379,
"redis.username": "",
"redis.password": "",
"redis.db": 0,
"redis.key-prefix": "",
"redis.channel-name": "liquid"
}

Configuration Details:

OptionTypeDefaultDescription
privilege.can-use-cachebooleantrueEnables Redis caching and pub/sub functionality
redis.hoststring"127.0.0.1"Redis server hostname or IP address
redis.portnumber6379Redis server port number
redis.usernamestring""Redis username (Redis 6.0+)
redis.passwordstring""Redis password for authentication
redis.dbnumber0Redis database number (0-15)
redis.key-prefixstring""Prefix for all Redis keys
redis.channel-namestring"liquid"Channel name for pub/sub events

Redis Security Configuration​

For production deployments, configure Redis with authentication:

Redis Configuration (redis.conf):

# Enable authentication
requirepass your-strong-password

# Bind to specific interface (not 0.0.0.0 in production)
bind 127.0.0.1

# Disable dangerous commands
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""

Liquid Configuration:

{
"redis.host": "your-redis-host",
"redis.port": 6379,
"redis.password": "your-strong-password"
}

RabbitMQ Configuration Options​

All RabbitMQ configuration options for Liquid:

{
"privilege.can-use-rabbitmq": true,
"rabbitmq.connectionString": "amqp://username:password@localhost:5672",
"rabbitmq.channel-name": "liquid"
}

Configuration Details:

OptionTypeDefaultDescription
privilege.can-use-rabbitmqbooleanfalseEnables RabbitMQ functionality
rabbitmq.connectionStringstring"amqp://localhost:5672"Full RabbitMQ connection string
rabbitmq.channel-namestring"liquid"Queue name for events

RabbitMQ Connection String Format​

The connection string supports various formats:

# Basic connection
amqp://localhost:5672

# With authentication
amqp://username:password@localhost:5672

# With vhost
amqp://username:password@localhost:5672/vhost

# SSL/TLS connection
amqps://username:password@localhost:5671

# With connection parameters
amqp://username:password@localhost:5672?heartbeat=60&connection_timeout=10000

RabbitMQ Security Configuration​

For production deployments, configure secure RabbitMQ connections:

Production Configuration:

{
"rabbitmq.connectionString": "amqp://liquid_user:strong_password@rabbitmq-server:5672/liquid_vhost"
}

Webhook Configuration Options​

All Webhook configuration options for Liquid Push Events:

{
"privilege.can-use-webhook-pusher": true,
"pusher.webhook.url": "https://api.your-app.com/liquid-events",
"pusher.webhook.secret": "your_secure_secret",
"webhook.timeout": 5000
}

Configuration Details:

OptionTypeDefaultDescription
privilege.can-use-webhook-pusherbooleanfalseEnables Webhook functionality
pusher.webhook.urlstring""Webhook URL for POST requests
pusher.webhook.secretstring""Secret key for generating HMAC
webhook.timeoutnumber5000Timeout (ms) for the request

Webhook Verification and Retries​

When Liquid sends a webhook event, it uses a standard POST request with the Content-Type: application/json header. It also includes an X-Webhook-Signature header containing an HMAC SHA-256 signature of the raw JSON payload. This signature allows you to verify that the webhook was genuinely sent by your Liquid instance and hasn't been tampered with.

If the webhook request fails or the receiving server responds with a non-2xx HTTP status code, Liquid will automatically retry the request up to 3 times using an exponential backoff strategy (1s, 2s, 4s).

Verifying the Signature (Node.js Example):

const crypto = require("crypto");

// Middleware to verify Liquid Webhooks
function verifyLiquidWebhook(req, res, next) {
const signature = req.headers["x-webhook-signature"];
const secret = "your_secure_secret"; // Matches pusher.webhook.secret

// Note: For this to work correctly, you must use the raw string body of the request.
// Assuming req.rawBody contains the raw JSON string before parsing.
const payloadString = req.rawBody || JSON.stringify(req.body);

const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payloadString)
.digest("hex");

if (signature === expectedSignature) {
next(); // Signature is valid
} else {
res.status(401).send("Invalid signature");
}
}

Event Filtering​

You can control which events are published by configuring the system.push-events array. Only events listed in this array will be published to the queue.

Event Prefixing​

You can add a prefix to all event names by setting:

{
"system.push-events.prefix": "myapp."
}

This will prefix all events (e.g., user.login becomes myapp.user.login).

Available Events​

User Events​

user.create​

Published when a new user account is created.

Data Structure:

{
"id": "uuid-v4",
"name": "user.create",
"data": {
"user": {
"_id": "user_id",
"username": "john_doe",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"verified": false,
"role": "user",
"credits": 100,
"scope": ["user:profile:read"],
"createdAt": "2023-01-01T10:00:00.000Z"
}
}
}

user.login​

Published when a user successfully logs in (both password and OAuth logins).

Data Structure:

{
"id": "uuid-v4",
"name": "user.login",
"data": {
"user": {
"_id": "user_id",
"username": "john_doe",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe"
}
}
}

user.logout​

Published when a user logs out from a single session.

Data Structure:

{
"id": "uuid-v4",
"name": "user.logout",
"data": {
"user": {
"_id": "user_id",
"username": "john_doe",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe"
}
}
}

user.logout-all​

Published when a user logs out from all sessions.

Data Structure:

{
"id": "uuid-v4",
"name": "user.logout-all",
"data": {
"user": {
"_id": "user_id",
"username": "john_doe",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe"
}
}
}

Social Events​

user.follow​

Published when a user successfully follows another user (for public profiles).

Data Structure:

{
"id": "uuid-v4",
"name": "user.follow",
"data": {
"source": "follower_user_id",
"target": "followed_user_id"
}
}

user.follow-request​

Published when a user requests to follow a private profile.

Data Structure:

{
"id": "uuid-v4",
"name": "user.follow-request",
"data": {
"source": "requester_user_id",
"target": "target_user_id"
}
}

user.unfollow​

Published when a user unfollows another user.

Data Structure:

{
"id": "uuid-v4",
"name": "user.unfollow",
"data": {
"source": "unfollower_user_id",
"target": "unfollowed_user_id"
}
}

Email Events​

email.send​

Published when an email is sent through the Pusher email adapter. This is useful for implementing custom email sending logic.

Data Structure:

{
"id": "uuid-v4",
"name": "email.send",
"data": {
"email": {
"to": "recipient@example.com",
"from": {
"email": "noreply@yourapp.com",
"name": "Your App"
},
"subject": "Welcome to Your App",
"text": "Plain text content",
"html": "<h1>HTML content</h1>",
"templateId": "template_id",
"dynamicTemplateData": {
"name": "John",
"verification_url": "https://yourapp.com/verify?token=abc123"
},
"timestamp": "2023-01-01T10:00:00.000Z"
}
}
}
info

The email.send event is only published when using the pusher email adapter. Configure this by setting system.email-adapter to "pusher" in your backend configuration.

Event Structure​

All events follow a consistent structure:

{
"id": "unique-uuid-v4-identifier",
"name": "event.name",
"data": {
// Event-specific data
}
}
  • id: A unique UUID v4 identifier for each event
  • name: The event name (with optional prefix)
  • data: Event-specific payload containing relevant information

Consuming Events​

Redis Pub/Sub Example (Node.js)​

const Redis = require("ioredis");
const redis = new Redis({
host: "localhost",
port: 6379,
});

redis.subscribe("liquid");

redis.on("message", (channel, message) => {
const event = JSON.parse(message);

switch (event.name) {
case "user.login":
console.log(`User ${event.data.user.username} logged in`);
break;
case "user.follow":
console.log(`User ${event.data.source} followed ${event.data.target}`);
break;
case "email.send":
// Custom email sending logic
sendCustomEmail(event.data.email);
break;
}
});

RabbitMQ Example (Node.js)​

const amqp = require("amqplib");

async function consumeEvents() {
const connection = await amqp.connect("amqp://localhost");
const channel = await connection.createChannel();
const queue = "liquid";

await channel.assertQueue(queue);

channel.consume(queue, (message) => {
if (message) {
const event = JSON.parse(message.content.toString());

switch (event.name) {
case "user.create":
console.log(`New user created: ${event.data.user.username}`);
break;
case "user.logout-all":
console.log(`User ${event.data.user.username} logged out from all sessions`);
break;
}

channel.ack(message);
}
});
}

consumeEvents();

Dependencies​

DependencyRequiredUsed For
RedisOptionalRedis Pub/Sub adapter and caching
RabbitMQOptionalRabbitMQ message queue adapter
WebhookOptionalHTTP webhook pusher adapter

Redis Dependencies​

For Redis Pub/Sub:

  • Ensure privilege.can-use-cache is enabled
  • Redis connection must be properly configured
  • Uses the same Redis instance as Liquid's caching system

RabbitMQ Dependencies​

For RabbitMQ:

  • Ensure privilege.can-use-rabbitmq is enabled
  • RabbitMQ connection string must be properly configured
  • Independent message queue system

Webhook Dependencies​

For Webhook:

  • Ensure privilege.can-use-webhook-pusher is enabled
  • A valid pusher.webhook.url and pusher.webhook.secret must be configured
  • The target server must be able to receive POST requests and return 2xx HTTP codes
tip

Events are published asynchronously and won't block the main API response. However, if the message queue is unavailable, events will be silently dropped. Ensure your message queue infrastructure is reliable for production use.

warning

Event payloads may contain sensitive user information. Ensure your message queue and event consumers are properly secured, especially in production environments.