Messaging Endpoints
The Messaging module manages real-time conversations, chat messages, push notifications, SMS/email delivery, WebSocket connections, private messaging, device registration, and texting providers. It provides the communication layer used across all ChurchApps applications for both live streaming chat and asynchronous notifications.
Base path: /messaging
Conversations
Base path: /messaging/conversations
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /timeline/ids?ids= | JWT | — | Load conversations by comma-separated IDs with first/last messages |
| GET | /messages/:contentType/:contentId | JWT | — | Load conversations for content with paginated messages (?page=&limit=) |
| GET | /posts | JWT | — | Get post-type conversations for the current user's groups |
| GET | /posts/group/:groupId | JWT | — | Get post-type conversations for a specific group |
| GET | /notes/feed?section=&limit=&before= | JWT | — | Newest notes across every record for the B1 Admin Notes page. section is people, groups, events or workflows (400 otherwise); limit defaults to 25 (max 100); before is the cursor from the previous page. Each note still passes its own type's rule (People Edit / confidential notes permission, leaders of the group, event viewers, workflow card viewers), so callers only see notes they could open. Note text is cut to 300 characters |
| GET | /current/:churchId/:contentType/:contentId | Public | — | Get or create the current conversation for content (auto-decrypts contentId) |
| GET | /:churchId/:contentType/:contentId | Public | — | Load conversations by content type and ID |
| GET | /:churchId/:id | Public | — | Load a single conversation by ID |
| POST | / | JWT | — | Create or update conversations (batch) |
| POST | /start | JWT | — | Start a new conversation with an initial comment message |
| DELETE | /:churchId/:id | JWT | — | Delete a conversation |
Person notes access control
Conversations with contentType: "person" (the Notes tab on a person record) or contentType: "personConfidential" (the Confidential Notes section) are gated on every read and write path, including the otherwise-public routes above, which return 401 for these content types. person requires the MembershipApi People / Edit permission; personConfidential requires People / View Confidential Notes. For scoped API keys, people:write carries both actions (the key's user must still hold the underlying role permission).
Group and event notes access control
Two more note content types are gated the same way, on every read and write path (including the public routes above), and are never open to anonymous posting:
groupNote(contentId= group ID) -- the Leader notes feed on a group. Only that group's leaders (leaderGroupIdson the JWT) can read or post. No staff permission grants access, includingcontent.editand the group-membership permissions.eventNote(contentId= event ID) -- the notes on a calendar event. Staff withcontent.editor Calendars / Admin can always read and post. Otherwise access follows the event'snotesViewRole(read) andnotesPostRole(post):staff(the default, used when the field isnull) allows no one else,leadersallows leaders of the event's group, andmembersallows the event group's members and leaders. An event with nogroupIdis staff-only.
Workflow card notes access control
Conversations with contentType: "workflowCard" (the notes on a workflow card, where contentId is the card's task ID) are readable and writable only by someone who can open that card: a user with the DoingApi Tasks / View permission, or the person or group the card is assigned to, created by, or about. Everyone else gets 401 on every read path, including the public routes above. A card's conversation is attached through the card itself with POST /doing/tasks/:id/conversation (body { "conversationId": "..." }; the first conversation attached wins), which the card's editors and its assignee can call.
Example: Start a Conversation
POST /messaging/conversations/start
Authorization: Bearer <token>
{
"groupId": "group-123",
"contentType": "group",
"contentId": "group-123",
"title": "Weekly Discussion",
"comment": "Welcome to this week's discussion thread!"
}
{
"id": "conv-456",
"churchId": "church-789",
"contentType": "group",
"contentId": "group-123",
"title": "Weekly Discussion",
"dateCreated": "2026-02-17T10:00:00.000Z",
"visibility": "public",
"allowAnonymousPosts": false,
"groupId": "group-123"
}
Messages
Base path: /messaging/messages
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /conversation/:conversationId | JWT | — | Load all messages for a conversation |
| GET | /catchup/:churchId/:conversationId | Public | — | Load all messages for a conversation (public catchup for live chat) |
| GET | /:churchId/:id | Public | — | Load a single message by ID |
| POST | / | JWT | — | Save messages (batch). Sends real-time updates and triggers notifications. Updating an existing message requires being its author or holding content.edit; the stored author is never reassignable |
| POST | /send | Public | — | Send messages (batch, public). Sends real-time updates via WebSocket and triggers notifications |
| POST | /setCallout | JWT | — | (legacy) Broadcast a callout message in real time. No active client; live stream chat no longer renders callouts |
| DELETE | /:churchId/:id | JWT | — | Delete a message and broadcast the deletion in real time. See Message moderation |
Message moderation
Deleting a message is allowed for:
- the message's author;
- staff with
content.edit(anywhere in the church); - group leaders, for conversations with a
contentTypeofgrouporgroupAnnouncementwhosecontentIdis a group they lead (leaderGroupIdson the JWT).
Person-note conversations (person / personConfidential) are never leader-moderated — they use the notes permissions (people.edit, people.viewConfidentialNotes) instead.
Leaders get delete only, not edit: rewriting another member's message stays restricted to the author and content.edit staff.
Example: Send a Message
POST /messaging/messages/send
[
{
"churchId": "church-789",
"conversationId": "conv-456",
"personId": "person-123",
"displayName": "John Smith",
"content": "Hello everyone!",
"messageType": "comment"
}
]
[
{
"id": "msg-001",
"churchId": "church-789",
"conversationId": "conv-456",
"personId": "person-123",
"displayName": "John Smith",
"timeSent": "2026-02-17T10:05:00.000Z",
"content": "Hello everyone!",
"messageType": "comment"
}
]
Private Messages
Base path: /messaging/privatemessages
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | / | JWT | — | Load all private messages for the current user (includes last message per conversation, marks all as read) |
| GET | /existing/:personId | JWT | — | Find an existing private conversation with a specific person |
| GET | /:id | JWT | — | Load a private message by ID (clears notification if addressed to current user) |
| POST | / | JWT | — | Send private messages (batch). Triggers push notification to recipient |
Notifications
Base path: /messaging/notifications
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /unreadCount | JWT | — | Get unread notification count for the current user |
| GET | /my | JWT | — | Load all notifications for the current user (marks all as read) |
| GET | /tmpEmail | Public | — | Trigger daily email notification digest (debug/cron endpoint) |
| GET | /:churchId/person/:personId | JWT | — | Load notifications for a specific person |
| GET | /:churchId/:id | JWT | — | Load a notification by ID |
| POST | / | JWT | — | Create or update notifications (batch) |
| POST | /create | JWT | — | Create notifications for multiple people. Body: { peopleIds, contentType, contentId, message, link } |
| POST | /markRead/:churchId/:personId | JWT | — | Mark all notifications as read for a person |
| POST | /sendTest | JWT | — | Send a test push notification. Body: { personId, title } |
| POST | /ping | Public | — | Create a notification from an external trigger. Body: { personId, churchId, contentType, contentId, message, triggeredByPersonId } |
| DELETE | /:churchId/:id | JWT | — | Delete a notification |
Example: Create Notifications
POST /messaging/notifications/create
Authorization: Bearer <token>
{
"peopleIds": ["person-123", "person-456"],
"contentType": "group",
"contentId": "group-789",
"message": "New event posted in your group",
"link": "/groups/group-789"
}
Notification Preferences
Base path: /messaging/notificationpreferences
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /categories | JWT | — | List notification categories with their default and allowed channels |
| GET | /my | JWT | — | Load notification preferences for the current user (auto-creates defaults if none exist) |
| POST | / | JWT | — | Save the current user's preferences. Body: any of allowPush, allowSms, emailFrequency, masterMute, quietHoursStart, quietHoursEnd, timeZone, maxPushPerDay, plus optional overrides: [{ categoryKey, channel, optedIn }] (channel may be push, email, in_app, or sms; locked categories are ignored). allowSms is the member's opt-in for text reminders. A legacy array-of-rows body is still accepted |
Connections
Base path: /messaging/connections
Manages WebSocket/real-time connections for chat, group conversations, private messages, and live streaming. See Real-time Architecture for the end-to-end protocol.
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /:churchId/:conversationId | Public | — | Load all connections for a conversation |
| POST | / | Public | — | Register connections (batch). Triggers an attendance broadcast on the conversation. Body items: { churchId, conversationId, socketId, displayName?, personId? } |
| POST | /setName | Public | — | Update the display name for a connection by socket ID. Body: { socketId, name } |
| DELETE | /:churchId/:conversationId/:socketId | Public | — | Drop a connection from a conversation. Triggers an attendance broadcast |
| POST | /tmpSendAlert | Public | — | Send a notification alert to a person's connections. Body: { churchId, personId } |
Devices
Base path: /messaging/devices
Manages device registration for push notifications and content pairing (e.g., Lessons app on TV displays).
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| POST | /enroll | JWT | — | Enroll or update a device (mobile push registration). Matches by FCM token or device ID |
| POST | /enrollAnon | Public | — | Enroll an anonymous device and generate a 4-character pairing code |
| POST | / | Public | — | Save devices (batch) |
| GET | /pair/:pairingCode | JWT | — | Pair a device using its pairing code. Optional ?contentType=&contentId= to assign content |
| GET | /status/:deviceId | Public | — | Check pairing status of a device |
| GET | /:churchId | JWT | — | Load all devices for a church |
| GET | /:churchId/person/:personId | JWT | — | Load all devices for a person |
| GET | /:churchId/:id | JWT | — | Load a device by ID |
| DELETE | /:churchId/:id | JWT | — | Delete a device |
Example: Enroll a Device
POST /messaging/devices/enroll
Authorization: Bearer <token>
{
"fcmToken": "firebase-token-abc123",
"appName": "B1Mobile",
"label": "John's iPhone",
"deviceInfo": "iOS 17, iPhone 15"
}
{
"id": "device-001",
"churchId": "church-789",
"fcmToken": "firebase-token-abc123",
"appName": "B1Mobile",
"label": "John's iPhone",
"registrationDate": "2026-02-17T10:00:00.000Z",
"lastActiveDate": "2026-02-17T10:00:00.000Z"
}
Device Contents
Base path: /messaging/devicecontents
Manages content assignments for paired devices (e.g., which lesson is displayed on a TV).
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /deviceId/:deviceId | JWT | — | Load content assignments for a device |
| POST | / | JWT | — | Save device content assignments (batch) |
| DELETE | /:id | JWT | — | Delete a device content assignment |
Texting
Base path: /messaging/texting
Manages SMS texting providers, group text messaging, and delivery tracking.
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /providers | JWT | — | Load texting providers for the church (credentials are masked) |
| GET | /status | JWT | — | { enabled } — whether the church has an enabled texting provider. Any signed-in church user; used to show Text options only when texts can be sent |
| GET | /inboundUrl | JWT | texting.send or settings.edit | { url } — the church's inbound (STOP) webhook URL to paste into the provider |
| POST | /inbound/:churchId/:token | Public | — | Provider inbound-message webhook. token is an HMAC of the church id. A reply that is exactly a stop word (STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, OPTOUT, REVOKE) marks every matching person optedOut and turns off their allowSms. Returns { optedOut: <count> }; other replies return { optedOut: 0 } |
| GET | /preview/:groupId | JWT | — | Preview recipients for a group text (eligible, opted-out, no-phone counts) |
| GET | /sent | JWT | — | Load all sent text message records for the church |
| GET | /sent/:id/details | JWT | — | Load a sent text with per-recipient delivery logs |
| POST | /providers | JWT | — | Save texting providers (batch). Encrypts API credentials |
| POST | /send | JWT | — | Send an SMS to all eligible members of a group. Body: { groupId, message }. Merge fields ({{firstName}}, {{lastName}}, {{displayName}}, {{churchName}}) are resolved per recipient |
| POST | /sendPerson | JWT | — | Send an SMS to a single person. Body: { personId, phoneNumber, message }. Merge fields are resolved, and the resolved text is what gets logged |
| DELETE | /providers/:id | JWT | — | Delete a texting provider |
Example: Send Group Text
POST /messaging/texting/send
Authorization: Bearer <token>
{
"groupId": "group-123",
"message": "Reminder: Service starts at 10 AM this Sunday!"
}
{
"totalMembers": 50,
"recipientCount": 42,
"successCount": 40,
"failCount": 2,
"optedOutCount": 5,
"noPhoneCount": 3
}
Email Templates
Base path: /messaging/emailTemplates
Manages reusable email templates and sending templated emails to groups.
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | / | JWT | — | Load all email templates for the church |
| GET | /:id | JWT | — | Load a single email template by ID |
| GET | /sendStatus | JWT | groupMembers.edit or tasks.admin | The church's group email status: { approved, paused, remaining, requested }. B1Admin's Send Email dialog and the workflow Send email action use it to warn when email won't go out |
| POST | /requestApproval | JWT | groupMembers.edit | Ask support to approve the church for group email (at most once per church per week) |
| GET | /preview/:groupId | JWT | — | Preview email delivery for a group (eligible recipient count, members with no email) |
| POST | / | JWT | — | Create or update email templates (batch) |
| POST | /send | JWT | — | Send a templated email to all members of a group. Body: { groupId, subject, htmlContent } |
| DELETE | /:id | JWT | — | Delete an email template |
Example: Send Email to Group
POST /messaging/emailTemplates/send
Authorization: Bearer <token>
{
"groupId": "group-123",
"subject": "This Week's Update - {{churchName}}",
"htmlContent": "<p>Hello {{firstName}},</p><p>Here's what's happening this week...</p>"
}
{
"totalMembers": 50,
"recipientCount": 45,
"successCount": 44,
"failCount": 1,
"noEmailCount": 5
}
Supported merge fields: {{firstName}}, {{lastName}}, {{displayName}}, {{email}}, {{churchName}}
Blocked IPs
Base path: /messaging/blockedips
(legacy) IP-blocking for live streaming chat. The B1App client no longer calls POST / — IP blocking was removed in the unified-delivery migration. The /clear route is still invoked server-to-server by StreamingServiceController when streaming services are saved.
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| POST | / | JWT | — | (legacy) Save blocked IPs (batch). No active client |
| POST | /clear | JWT | — | Clear all blocked IPs for specific services. Body: [{ serviceId, churchId }] |
Delivery Logs
Base path: /messaging/deliverylogs
Tracks delivery status for sent messages (SMS, push notifications, email).
| Method | Path | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /content/:contentType/:contentId | JWT | — | Load delivery logs by content type and ID |
| GET | /person/:personId | JWT | — | Load delivery logs for a person. Optional ?startDate=&endDate= filters |
| GET | /recent | JWT | — | Load recent delivery logs for the church. Optional ?limit= (default 100) |
| GET | /:id | JWT | — | Load a delivery log by ID |
Related Pages
- Real-time Architecture -- WebSocket protocol, room subscriptions, and the unified delivery framework
- Web Push Notifications -- Browser push enrollment and delivery
- Membership Endpoints -- People, groups, roles, and core identity
- Attendance Endpoints -- Service and visit tracking
- Authentication & Permissions -- Login flow, JWT, OAuth, permission model
- Module Structure -- Code organization patterns