Appearance
Push Notifications (A1)
The Chatter API sends push notifications through Apple Push Notification service (APNs) using your APNs key. You configure it once with the developer endpoints, and the app registers each device token.
When pushes are sent
| Trigger | Sent to | Condition |
|---|---|---|
| New message | Every other member's devices | Device has no open WebSocket, and the conversation is not muted |
| Friend request received | Recipient's devices | Device has no open WebSocket |
| Friend request accepted | Sender's devices | Device has no open WebSocket |
Devices with an open socket receive the WebSocket event instead, so the user never gets both.
Setup
1. Create an APNs key (once)
- In Apple Developer → Keys, create a key with Apple Push Notifications service (APNs) enabled.
- Download the
.p8file. Note the Key ID and your Team ID. - In Xcode, add the Push Notifications capability to your app target.
2. Register the key with your workspace (once, from your machine)
$API and $CK are set as in the Quickstart. Set CS to your clientSecret.
bash
export CS=cs_your_client_secret_here
curl -X PUT $API/developer/apns \
-H "X-Client-Key: $CK" -H "X-Client-Secret: $CS" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg key "$(cat AuthKey_ABC123DEFG.p8)" \
'{teamId:"TEAM123456", keyId:"ABC123DEFG", bundleId:"com.yourname.chatter", privateKey:$key}')"The API uses the right APNs environment for each device token based on the environment the app reports (next step). TestFlight and App Store builds use production. Debug builds from Xcode use sandbox.
Check the configuration:
bash
curl $API/developer/apns -H "X-Client-Key: $CK" -H "X-Client-Secret: $CS"
# {"configured":true,"teamId":"TEAM123456","keyId":"ABC123DEFG","bundleId":"com.yourname.chatter"}3. Register the device token (in the app)
After the user signs in and grants permission, call registerForRemoteNotifications(). Send the token (hex string) to the API:
PUT /me/devices/current/push-token
json
{ "token": "a1b2c3…", "environment": "production" }environment: sandbox for debug builds, production for TestFlight and App Store. 204. Call it on every launch — tokens can change.
DELETE /me/devices/current/push-token
Stops pushes to this device. POST /auth/logout does this automatically.
4. Send a test push
bash
curl -X POST $API/developer/test-push \
-H "X-Client-Key: $CK" -H "X-Client-Secret: $CS" \
-H 'Content-Type: application/json' -d '{"userId":"01JB…"}'
# {"sent":1,"failed":0,"results":[{"deviceId":"01JB8ZD…","status":"sent"}]}APNs error reasons (for example BadDeviceToken, BadEnvironmentKeyInToken, TopicDisallowed) are returned in results to help you debug.
Payloads
New message
json
{
"aps": {
"alert": { "title": "Ana Souza", "body": "See you at 8?" },
"sound": "default",
"badge": 3,
"thread-id": "01JE…",
"mutable-content": 1
},
"chatter": {
"type": "message.new",
"conversationId": "01JE…",
"messageId": "01JF…",
"seq": 42,
"senderId": "01JB…"
}
}badge: total unread messages for the user across all non-muted conversations.thread-id: groups notifications per conversation.- For groups,
titleis"<sender> @ <group title>". - For media,
bodyis"📷 Photo","🎤 Voice message","🎥 Video", or"📄 Document".
Friend request
json
{
"aps": { "alert": { "title": "New friend request", "body": "Ben Tan (@ben_tan) wants to be your friend" }, "sound": "default" },
"chatter": { "type": "friend_request.received", "requestId": "01JD…", "fromUserId": "01JC…" }
}Friend request accepted
json
{
"aps": { "alert": { "title": "Ben Tan", "body": "accepted your friend request" }, "sound": "default" },
"chatter": { "type": "friend_request.accepted", "requestId": "01JD…", "friendUserId": "01JC…" }
}Handling in the app
- Tap: read
chatter.typeand navigate —message.new→ the conversation;friend_request.*→ the requests screen or the friend. - Foreground: you usually receive the WebSocket event first. Suppress the banner for the conversation that is on screen.
- Badge: when the user reads messages, update the app icon badge locally (
UNUserNotificationCenter.setBadgeCount). - Delivered receipts: a push does not mean the app received the message. Send
receipt.deliveredonly after the message is stored locally (after the next sync).