Publishing messages
Send one JSON POST to a topic.
Endpoint
POST/topics/:name/messages
Example
For an open topic, publish with the fields you need.
curl -s http://localhost:7685/topics/alerts.disk/messages \
-H 'Content-Type: application/json' \
-d '{
"priority": 3,
"tags": ["prod", "storage"],
"payload": {
"title": "Disk full",
"body": "Volume /data is 95% full"
}
}'
pingd-cli messages publish \
--topic alerts.disk \
--title "Disk full" \
--body "Volume /data is 95% full" \
--priority 3 \
--tags prod,storage
import requests
requests.post(
"http://localhost:7685/topics/alerts.disk/messages",
json={
"priority": 3,
"tags": ["prod", "storage"],
"payload": {
"title": "Disk full",
"body": "Volume /data is 95% full",
},
},
)
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
payload.body | string | yes | Main message text |
payload.title | string | no | Short headline |
payload.subtitle | string | no | Secondary line (used by APNS) |
priority | 1–3 | no | Default 2. 1 low, 3 urgent |
tags | string[] | no | Up to 10 tags, 1–30 chars each, [a-zA-Z0-9_-] |
ttl | seconds | no | Time-to-live, max 30 days |
Auth
Pick the one that matches the topic and caller:
- User/account publish: bearer token of the owner, an admin, or a user with
wo/rwpermission:-H "Authorization: Bearer $TOKEN" - Share-token publish: topic share token with
woorrwaccess:-H "X-Topic-Token: tk_..." - Public publish: no auth header if the topic has
publicPublish=true.
ro is read-only. It works for listing, polling, SSE, and subscriptions, not publishing. See Permissions for the full access matrix.
Priority
1: low2: default3: urgent
Priority 3 maps to APNS interrupting notifications and Web Push
urgency hints. Most clients, including the dashboard, sort by priority.
Tags
Tags are short labels for filtering and display. Up to 10 per message, each 1–30 characters, alphanumeric plus dash and underscore. They show up in the dashboard, in the SSE stream, and in the data payload sent to push clients.
TTL and expiry
If ttl is set, pingd records expiresAt = now + ttl. Expired messages
are filtered lazily on read, and the dispatch worker marks deliveries that haven't been sent
yet as expired instead of pushing them. Use this when a message should be ignored
after a specific time.
ttl is 30 days (2,592,000 seconds).
Without a ttl, messages never expire.
Delivery semantics
Publishing stores the message and creates pending delivery rows for active
subscriptions. A background worker picks those rows up, sends through APNS or Web Push,
and marks each delivery delivered, failed, or expired.
SSE listeners receive messages live on /topics/:name/stream.
Inspecting deliveries
Once a message exists, you can list its delivery rows:
GET/messages/:id/deliveries
Each row has status, retryCount, and timestamps. Useful for debugging "why didn't my phone get the push?"
Listing messages
GET/topics/:name/messages
Read access uses the same rules as GET /topics/:name.
Expired messages are filtered out of the response.