Notifications
Notify members through CheckCourt without ever learning their contact data
Your app can send a member a short message. CheckCourt delivers it: in the member's CheckCourt inbox (the bell at the top of the navigation, next to the club's name) and, if they allow it, by email. Your app never sees an email address or phone number, and it never learns whether the member muted it.
curl -X POST "https://app.checkcourt.de/api/v1/app/notifications" \
-H "Authorization: Bearer cca_…" \
-H "Content-Type: application/json" \
-d '{
"recipient": "psn_4hQ2v0bD1xZk8yWm3nT7aLc9eRf",
"title": "Your ball machine is ready",
"body": "Court 3 from 5:30 pm. The balls are in the basket next to the net.",
"url": "/booking?date=2026-05-01",
"idempotency_key": "reservation-8812-ready"
}'{ "id": "ntf_k3x9…", "accepted": true }With the SDK:
import { sendNotification } from "@checkcourt/sdk";
// claims: a verified extension context token (viewer.user_id is a psn_… pseudonym
// unless the installation holds members:read)
const { id } = await sendNotification(client, {
recipient: claims.viewer.user_id!,
title: "Your ball machine is ready",
body: "Court 3 from 5:30 pm.",
url: "/booking?date=2026-05-01",
idempotencyKey: "reservation-8812-ready",
});Scope
Add notifications:send to tenantScopes (club app) or userScopes (member app):
{
"tenantScopes": ["bookings:read", "notifications:send"],
"userScopes": ["notifications:send"]
}notifications:send is an app-only scope. No club role holds it, not even
Administrator, so the person installing your app does not need it. For a member app the
member's consent is enough. Clubs see it as „Benachrichtigungen schicken“ (send
notifications), members as a promise that the app can reach them through CheckCourt
without learning their contact data. Adding it later is a new scope, so clubs and members
consent again before the new version runs.
Who you can notify
recipient takes only an id your app legitimately received. Everything else, including
ids of other clubs, members waiting for approval, removed members and ids your token may
not use, returns the same 404. You cannot probe who exists.
| Token | Accepted recipient |
|---|---|
Club app (cca_) | the CheckCourt user id, only if the installation holds members:read |
the pseudonym psn_… from an extension context, bound to this installation | |
the pairwise id usr_… from GET /api/v1/me, for members who connected your app | |
Member app (ccu_) | only the member who connected it: their pairwise usr_… id or their user id |
Pseudonyms are per installation: a psn_… from one club's installation does not work for
another.
Message
| Field | Rules |
|---|---|
recipient | see above |
title | 1 to 80 characters, one line of plain text |
body | 1 to 500 characters, plain text, line breaks are kept. HTML is shown as text |
url | optional. A path inside CheckCourt such as /booking?date=2026-05-01 (not /api/…), or an https link on an origin you registered: one of your redirect URIs or the listing's connect URL |
category | optional label for your own kind of message, ^[a-z0-9][a-z0-9_.-]{0,39}$ |
idempotency_key | optional, up to 100 visible ASCII characters |
Write in German, in the tone of the club: the title says what happened, the body what the member can do. Leave out greetings and signatures; CheckCourt shows your app's name and icon next to every message.
Delivery
- Inbox: always, unless the member switched off „In CheckCourt“ for your app.
- Email: unless the member switched off email for your app or for all apps. CheckCourt sends at most 3 emails per member and app per day; later messages that day only reach the inbox. The email shows your app's name, the title, the body and a button to the link, and tells the member how to mute your app.
- Sandbox clubs: emails only go to members of your developer organization. Invented sandbox members get the inbox entry only.
- Push: not yet.
The response is the same either way: { "id", "accepted": true }. It does not say
whether the member saw the message or got an email.
Limits
| Limit | Value |
|---|---|
| Per installation | 60 per minute, 1,000 per day |
| Per member and app | 10 per day |
Over a limit you get 429 with Retry-After in seconds. The usual per-token limit of
120 requests per minute applies as well.
Idempotency
Send an idempotency_key when you might retry. Within 24 hours the same key on the same
installation returns the first notification's id and sends nothing again, even if the
body differs.
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION | a field breaks the rules above, or an unknown field |
401 | UNAUTHORIZED | missing or expired token, app uninstalled or disconnected |
403 | FORBIDDEN | the token lacks notifications:send, or it is an API key |
404 | NOT_FOUND | no member your app may notify under this id |
422 | BUSINESS_RULE | the club paused notifications for your app |
429 | RATE_LIMITED | a limit was reached, see Retry-After |
Control by members and clubs
- Members mute your app per channel under Einstellungen → Benachrichtigungen von Apps (Settings → Notifications from apps), or switch off emails from all apps.
- Club admins see on your installation's page how many messages you sent in the last 30
days and can pause your notifications. While paused, every request fails with
422. The pause also covers members who connected your app. - After uninstall or disconnect your tokens stop working at once. Messages already delivered stay in the member's inbox with your app's name.
There are no webhooks for notifications: you won't learn whether a message was read.