Communication Style
Set icon_url to render the notification with iOS Communication Notification styling — a round avatar on the side, like an iMessage. Combine with thread_id for grouping in Notification Center.


Source identity
| Field | Type | Description |
|---|---|---|
source | string | Stable internal id — e.g. sonarr, github-actions, home-assistant. Used for filtering and analytics. |
source_display_name | string | The pretty version users see — e.g. Sonarr, GitHub Actions.
Shown above the title and used to group notifications under that source in iOS settings
and the inbox. |
icon_url | string | HTTPS avatar (max 2048 chars). Recommended ≤256×256 and ≤100 KB; the iOS extension rejects responses larger than 512 KB to protect its 24 MB memory budget. When set, iOS renders the notification in Communication Notification style — a round avatar floating on the side, like an iMessage. |
Communication Notifications were introduced in iOS 15 for messaging apps. PushWard uses the same APIs to give your push notifications a personality — the source avatar floats on the side instead of being embedded as a tiny attachment.
Example: Home Assistant doorbell
curl -X POST https://api.pushward.app/notifications \
-H "Authorization: Bearer hlk_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Front Door",
"subtitle": "Doorbell pressed · 13:45",
"body": "Someone is at the front door",
"level": "time-sensitive",
"source": "home-assistant",
"source_display_name": "Home Assistant",
"thread_id": "ha-doorbell",
"icon_url": "https://www.home-assistant.io/images/favicon-192x192.png",
"push": true
}'Threading with thread_id
When several notifications belong to the same "conversation," give them all the same thread_id. iOS stacks them as one expandable group in Notification Center — the
user sees a single row that opens to reveal the full list. They add up; nothing is
overwritten.
Good real-world threads:
- Every download for one show:
thread_id: "sonarr-the-bear" - Every message in one chat:
thread_id: "chat-42" - Every alert from one host:
thread_id: "alerts-prod-db-01"
# First episode
curl -X POST https://api.pushward.app/notifications \
-H "Authorization: Bearer hlk_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Sonarr",
"body": "The Bear S03E01 downloaded",
"thread_id": "sonarr-the-bear",
"source": "sonarr",
"source_display_name": "Sonarr",
"push": true
}'
# Second episode — appears stacked under the first
curl -X POST https://api.pushward.app/notifications \
-H "Authorization: Bearer hlk_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Sonarr",
"body": "The Bear S03E02 downloaded",
"thread_id": "sonarr-the-bear",
"source": "sonarr",
"source_display_name": "Sonarr",
"push": true
}'Replacement with collapse_id
When you only care about the latest value of something, send each update with the same collapse_id. APNs replaces the previous notification on-device — the user always
sees one row, and it keeps changing.
Good real-world collapses:
- CI build progress:
collapse_id: "build-482"with body cycling through "running 12%" → "running 84%" → "passed". - Live score updates:
collapse_id: "match-arsenal-chelsea". - Battery / charge percent on a 3D print:
collapse_id: "printer-x1c".
collapse_id is a pure APNs delivery hint — PushWard does not store it,
and it's not returned in the response. Keep it under 64 characters.
# Build kicks off
curl -X POST https://api.pushward.app/notifications \
-H "Authorization: Bearer hlk_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Build #482",
"body": "Status: running (12%)",
"collapse_id": "build-482",
"source": "github-actions",
"source_display_name": "GitHub Actions",
"push": true
}'
# Final status — overwrites the previous notification on-device
curl -X POST https://api.pushward.app/notifications \
-H "Authorization: Bearer hlk_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Build #482",
"body": "Status: passed (100%)",
"collapse_id": "build-482",
"source": "github-actions",
"source_display_name": "GitHub Actions",
"push": true
}'Combine the two
Use thread_id and collapse_id together to get the best of both: every
build groups under one stack in Notification Center, while each individual build only ever
shows its latest status.
{
"title": "Build #482",
"body": "Status: passed (100%)",
"thread_id": "ci-builds",
"collapse_id": "build-482",
"source": "github-actions",
"source_display_name": "GitHub Actions",
"push": true
}