Channels & Audiences
Define named audiences (clubs, cohorts, sections) and target events and notifications at the users who have joined them.
Channels & Audiences
By default, every event and notification your app sends goes to the single user whose access token you used. Channels let you instead target a named group — a club, a cohort, a course section — so one request reaches everyone who has joined it.
This is how a club-style app reaches only its associated members: a user becomes a member of a channel only by opting in through your app under their own token. Your app cannot add arbitrary users, so channel messages never reach people who haven't joined.
Need to reach every Campus One user instead? That's the
"all"audience, gated by the admin-only broadcast capability — you don't need a channel for it.
Concepts
| Term | Meaning |
|---|---|
| Channel | A named audience you define, identified by a stable key unique within your app (e.g. chess-club). |
| Membership | A user's opt-in to a channel. Created/removed only for the token's own user. |
| Targeting | Pass audience: { "channel": "<key>" } when sending an event or notification to reach the channel's members. |
All channel endpoints require the events or notifications scope (the same capability used to send).
Typical flow
- Your app creates a channel once (idempotent):
POST /api/apps/channels. - When a user joins the club/section in your app, you enrol them:
POST /api/apps/channels/{key}/joinusing that user's access token. - You send to the channel:
POST /api/apps/events(or/notifications) withaudience: { "channel": "<key>" }. - Only members see it — on their dashboard, calendar, or notification inbox.
REST API Reference
Create or update a channel
POST /api/apps/channels — idempotent on key.
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Stable identifier, unique within your app (max 64 chars). |
name | string | Yes | Human-readable name (max 120 chars). |
description | string | No | Optional description (max 500 chars). |
List your channels
GET /api/apps/channels — returns every channel your app has defined.
Join / leave (acts on the token's user)
POST /api/apps/channels/{key}/join — subscribes the authenticated user. Idempotent.
POST /api/apps/channels/{key}/leave — unsubscribes the authenticated user. Idempotent (succeeds even if they weren't a member).
Both return { "channelKey": "<key>", "joined": true | false }.
Example
# 1. Define the channel (once)
curl -X POST https://auth.campusone.com.ng/api/apps/channels \
-H "Authorization: Bearer c1_act_abc123xyz" \
-H "Content-Type: application/json" \
-d '{ "key": "chess-club", "name": "Chess Club" }'
# 2. Enrol the signed-in user (their token)
curl -X POST https://auth.campusone.com.ng/api/apps/channels/chess-club/join \
-H "Authorization: Bearer c1_act_user_token"
# 3. Notify the whole channel
curl -X POST https://auth.campusone.com.ng/api/apps/notifications \
-H "Authorization: Bearer c1_act_abc123xyz" \
-H "Content-Type: application/json" \
-d '{ "title": "Ladder night", "body": "7pm in the JCR", "audience": { "channel": "chess-club" } }'const base = 'https://auth.campusone.com.ng/api/apps';
const headers = (token) => ({
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
});
// Define once
await fetch(`${base}/channels`, {
method: 'POST',
headers: headers(APP_TOKEN),
body: JSON.stringify({ key: 'chess-club', name: 'Chess Club' }),
});
// Enrol the current user (their token)
await fetch(`${base}/channels/chess-club/join`, {
method: 'POST',
headers: headers(userToken),
});
// Send to the channel
await fetch(`${base}/events`, {
method: 'POST',
headers: headers(APP_TOKEN),
body: JSON.stringify({
title: 'Ladder night',
startsAt: '2026-06-15T18:00:00Z',
audience: { channel: 'chess-club' },
}),
});Errors
| Status | Cause |
|---|---|
403 | Token lacks both the events and notifications scopes. |
404 | join/leave/targeting referenced a channel key that doesn't exist for your app — create it first. |
How membership scopes delivery
Because join/leave always act on the token's own user, your app can only ever enrol people who have signed in through it and chosen to join. Combined with channel targeting, this guarantees a club app's messages reach only that club's members — Campus One enforces it, so you don't have to maintain a separate allow-list of who may be messaged.