Headers
Every HTTP header the PushMesh API accepts and emits, including the proprietary X-Pm-* set.
Headers
PushMesh uses its own namespace, X-Pm-* (and X-PM-* on webhook deliveries).
Anything not on this page is a plain HTTP header.
Headers the API accepts
| Header | Routes | What it does |
|---|---|---|
Authorization | authenticated routes | Basic pm_live_… (app key) or Bearer <platform key> (application creation and key rotation). See Authentication and errors. |
Content-Type: application/json | routes with a body | the body is JSON. |
Idempotency-Key | POST /api/v1/notifications | the same key never fires two campaigns. It beats the body’s idempotency_key, which in turn beats external_id. |
X-Pm-Priority | POST /api/v1/notifications | transactional (high) or marketing (normal). The body’s priority field, when present, beats the header. Unknown value ⇒ named 400. |
X-Dry-Run: true | POST /api/v1/notifications | rehearsal: resolves the target and counts recipients, but persists nothing, delivers nothing and does not consume the idempotency key. |
If-None-Match | GET /api/v1/inapp/pending | if the presented ETag still holds, the answer is 304 with no body. |
Example: a rehearsal before firing for real
curl -X POST https://api.pushmesh.io/api/v1/notifications \
-H "Authorization: Basic $PUSHMESH_KEY" \
-H "Content-Type: application/json" \
-H "X-Dry-Run: true" \
-d '{
"app_id": "0198e7c4-77a1-7bd2-b0f1-6b1d3f2a9c40",
"included_segments": ["Subscribed Users"],
"contents": { "en": "Rehearsal — nothing was delivered" }
}'
{ "id": "", "recipients": 12840, "dry_run": true }
Headers the API emits
| Header | Where | What it carries |
|---|---|---|
X-Pm-Request-Id | every response, success or error | UUIDv7 of the request. Same value as explain.request_id. Keep it: it correlates your call with the service’s trace. |
X-Pm-Lane | POST /api/v1/notifications | the dispatch lane chosen: 0 = transactional, 1 = marketing. |
X-Pm-Recipients-Exato | POST /api/v1/notifications | true when recipients is an exact count (identifier targeting); false when it is an estimate (segment targeting). |
X-Idempotent-Replay | POST /api/v1/notifications | present and true only when the response is a byte-for-byte replay of an earlier call with the same Idempotency-Key. |
ETag | GET /api/v1/inapp/pending | fingerprint of the In-App package. Send it back in If-None-Match next session. The body repeats it in the etag field. |
Retry-After | 429 on device routes | seconds to wait (60). See the note in Limits. |
X-Pm-Instance | responses that pass through the edge | identifier of the instance that served you — support information. |
Reading a send result from the headers
HTTP/1.1 200 OK
X-Pm-Request-Id: 0198f0b2-6e31-7a4c-9f10-2c9a1d4e7b55
X-Pm-Lane: 0
X-Pm-Recipients-Exato: true
Content-Type: application/json
{"id":"0198e7c4-77a1-7bd2-b0f1-6b1d3f2a9c40","recipients":2,"external_id":"cmp-123"}
Repeat the call with the same Idempotency-Key and the same body and the body
comes back identical, now with X-Idempotent-Replay: true — that is how you
tell, in your own logs, a second campaign from a safe retry.
Headers on webhook deliveries
When PushMesh calls your endpoint, the request carries:
| Header | Content |
|---|---|
Content-Type | application/json |
X-PM-Evento | delivery.received, delivery.clicked, notification.completed or teste.ping |
X-PM-Entrega-Id | identifier of this delivery. It is your deduplication key. |
X-PM-Canal | webhook — which channel this confirmation went out on. The same value travels inside the signed body. |
X-PM-Assinatura | sha256=<hex(HMAC-SHA256(secret, body))> |
Verification details in Webhooks.
CORS
The API answers with permissive CORS: any origin, any method, any header,
max-age of 3600 seconds — and no credentials.
That is safe by construction here because no route authenticates via cookie:
the device identifies itself with app_id + a per-IP ceiling, the API with a key
in a header, the dashboard with a Bearer token. With no ambient authority in the
browser there is no CSRF — the browser merely gains the ability to read what
curl always could.
The practical consequence is serious: a pm_live_ key must never go into
browser code or inside an APK/IPA. Open CORS is not permission to expose the
key; it is permission to try the API from a documentation page. Authenticated
calls belong on your server.