Skip to content

Broadcast Notifications

Send a notification to all active users at a scheduled time using a one-off event

one-off event broadcast notifications scheduled event webhook handler example

A one-off scheduled event fires at a specific date and time. The handler sends a broadcast notification to all active users. You can see this in action in the react-demo example, see Notifications.tsx.

./functions/events/broadcast-notification.ts
import { createHash, timingSafeEqual } from 'node:crypto'
import { createClient, withAdminSession } from '@nhost/nhost-js'
import type { Request, Response } from 'express'
const hash = (value: string) => createHash('sha256').update(value).digest()
export default async (req: Request, res: Response) => {
// Validate the webhook secret — see the Webhook Security guide
const webhookSecret = req.headers['nhost-webhook-secret'] as string | undefined
const expected = process.env.NHOST_WEBHOOK_SECRET
if (
!webhookSecret ||
!expected ||
!timingSafeEqual(hash(webhookSecret), hash(expected))
) {
return res.status(401).json({ message: 'Unauthorized' })
}
// One-off events and cron triggers deliver their payload at req.body.payload
const payload = req.body.payload
if (!payload) {
return res.status(400).json({ message: 'No payload' })
}
// Extract the broadcast details from the payload configured in the dashboard
const { title, message } = payload
const type = payload.type || 'announcement'
if (!title) {
return res.status(400).json({ message: 'Missing required field: title' })
}
if (!message) {
return res.status(400).json({ message: 'Missing required field: message' })
}
// Create an admin client to query the database
const nhost = createClient({
region: process.env.NHOST_REGION,
subdomain: process.env.NHOST_SUBDOMAIN,
configure: [
withAdminSession({
adminSecret: process.env.NHOST_ADMIN_SECRET,
}),
],
})
// Fetch all active (non-disabled) users
const { body } = await nhost.graphql.request<{
users: Array<{ id: string }>
}>({
query: `
query GetActiveUsers {
users(where: { disabled: { _eq: false } }) {
id
}
}
`,
})
if (body.errors) {
return res.status(500).json({ errors: body.errors })
}
const activeUsers = body.data?.users || []
if (activeUsers.length === 0) {
return res.status(200).json({ message: 'No active users found' })
}
// Create a notification for every active user
const notifications = activeUsers.map((u) => ({
user_id: u.id,
title,
message,
type,
}))
const insertResult = await nhost.graphql.request<{
insert_notifications: { affected_rows: number } | null
}>({
query: `
mutation InsertNotifications($objects: [notifications_insert_input!]!) {
insert_notifications(objects: $objects) {
affected_rows
}
}
`,
variables: { objects: notifications },
})
if (insertResult.body.errors) {
return res.status(500).json({ errors: insertResult.body.errors })
}
res.status(200).json({
message: `Broadcast sent to ${activeUsers.length} user(s)`,
})
}