Send a notification
This is the main webhook endpoint. The secret in the URL acts as the credential — no additional headers required.
POST /api/v1/:secret GET /api/v1/:secret?message=Hello
The :secret can be your device secret (sends to one device), your user secret (sends to all devices on your account), or a named webhook secret (a custom-labelled URL you create).
Request formats
Plain text — the entire body becomes the notification message:
curl -X POST https://api.notifikations.com/api/v1/YOUR_SECRET \ -d 'Deploy succeeded ✅'
JSON — full control over all fields:
curl -X POST https://api.notifikations.com/api/v1/YOUR_SECRET \ -H 'Content-Type: application/json' \ -d '{"message":"Tests passed","title":"CI","sound":"cha_ching"}'
GET — useful for webhooks that only support GET:
curl "https://api.notifikations.com/api/v1/YOUR_SECRET?message=Hello&title=Alert"
Payload fields
| Field | Type | Description |
|---|---|---|
message | string required | The notification body text. |
title | string | Bold title shown above the message. |
subtitle | string | Secondary line below the title. |
sound | string | default, none, or a custom sound name (see below). |
thread_id | string | Groups related notifications together in Notification Center. |
open_url | string | URL opened when the notification is tapped. |
image_url | string | Remote image displayed in the notification. |
interruption-level | string | passive, active, or time-sensitive. |
filter-criteria | string | iOS Focus filter matching value. |
expiration_date | ISO 8601 | Deadline after which APNs discards the notification. |
send_at | ISO 8601 | Schedule the notification for a future time (up to 30 days). See Scheduled delivery. |
category | string | Show action buttons on the notification. See Interactive actions. |
callback_url | string | URL the app will POST to when the user taps an action button. See Interactive actions. |
Available sounds
default · brrr · bell_ringing ·
bubble_ding · cha_ching ·
cat_meow · dog_barking ·
door_bell · duck_quack ·
upbeat_bells
Response
| Status | Body | Meaning |
|---|---|---|
200 | {"success":true,"apnsId":"…"} | Notification accepted by APNs. |
200 | {"success":true,"sent":2,"failures":0} | User secret fan-out result. |
400 | {"error":"message is required"} | Missing required field. |
404 | {"error":"webhook not found"} | Secret not registered. |
502 | {"error":"…"} | APNs rejected the notification. |
Scheduled delivery
Add a send_at field to any notification request to deliver it at a future time. The server stores the job and fires it automatically — your script or service does not need to stay running.
curl -X POST https://api.notifikations.com/api/v1/YOUR_SECRET \ -H 'Content-Type: application/json' \ -d '{"message":"Time to stand up","title":"Reminder","send_at":"2026-04-01T09:00:00.000Z"}'
GET requests also support it:
curl "https://api.notifikations.com/api/v1/YOUR_SECRET?message=Hello&send_at=2026-04-01T09:00:00.000Z"
Scheduled response
{ "success": true, "scheduled": true, "jobId": "uuid", "sendAt": "2026-04-01T09:00:00.000Z" }Limits & notes
send_atmust be a future ISO 8601 timestamp, within 30 days from now.- Maximum 10 pending scheduled jobs per webhook URL.
- Delivery precision is approximately ±1 minute (Cloudflare Cron resolution).
- Pro/trial status is evaluated at fire time, not at scheduling time.
- Scheduled notification content is stored on Cloudflare's infrastructure until sent, then deleted immediately. See the privacy policy.
Scheduled response codes
| Status | Body | Meaning |
|---|---|---|
200 | {"success":true,"scheduled":true,"jobId":"…","sendAt":"…"} | Job accepted and stored. |
400 | {"error":"send_at must be a valid future ISO 8601 timestamp"} | Timestamp is missing, malformed, or in the past. |
400 | {"error":"send_at cannot be more than 30 days in the future"} | Exceeds the 30-day limit. |
429 | {"error":"too_many_scheduled: max 10 pending jobs per webhook"} | Pending job cap reached. |
Named webhooks
By default your device secret and user secret are your webhook URLs. Named webhooks let you create additional, independently-labelled URLs — useful when you want separate endpoints for different tools or contexts ("CI", "Server", "Home automation") without sharing the same secret across all of them.
Create a named webhook
POST /api/v1/webhooksGenerate a new secret locally (e.g. ntf_ci_…), hash it with SHA-256, and register the digest:
curl -X POST https://api.notifikations.com/api/v1/webhooks \ -H 'Content-Type: application/json' \ -d '{"deviceDigest":"<sha256(deviceSecret)>","deviceToken":"<apnsToken>","webhookDigest":"<sha256(newSecret)>","label":"CI","scope":"device"}'
| Field | Type | Description |
|---|---|---|
deviceDigest | string required | SHA-256 digest of your device secret — proves ownership. |
deviceToken | string required | Your APNs device token — proves ownership. |
webhookDigest | string required | SHA-256 digest of the new secret you generated locally. |
label | string required | Human-readable name (max 64 chars). |
scope | string | device (default) — sends to this device only. user — fans out to all your devices. |
On success: {"success":true}. The new webhook URL is then https://api.notifikations.com/api/v1/YOUR_NEW_SECRET and behaves exactly like your regular webhook.
List named webhooks
GET /api/v1/webhooks?deviceDigest=<digest>&deviceToken=<token>Returns all named webhooks registered to a device:
{
"webhooks": [
{ "webhookDigest": "abc…", "label": "CI", "scope": "device", "createdAt": "2026-03-30T…" },
{ "webhookDigest": "def…", "label": "Home", "scope": "user", "createdAt": "2026-03-30T…" }
]
}Delete a named webhook
DELETE /api/v1/webhooks/:webhookDigest
curl -X DELETE https://api.notifikations.com/api/v1/webhooks/<webhookDigest> \ -H 'Content-Type: application/json' \ -d '{"deviceDigest":"<sha256(deviceSecret)>","deviceToken":"<apnsToken>"}'
Immediately invalidates the URL. The raw secret you generated was never stored, so there is nothing else to revoke.
Limits & notes
- Maximum 10 named webhooks per device.
- Each named webhook has its own independent secret — rotating your device or user secret does not affect them.
- Deleting or unregistering a device automatically deletes all named webhooks owned by that device.
- Named webhook URLs support all the same payload fields and scheduled delivery as regular webhooks.
- Pro/trial status is checked at send time against the owner device.
Interactive actions
Add a category field to show tappable action buttons on the notification. When the user taps a button, the app POSTs the result to your callback_url — no server round-trip, no stored state.
Categories
| category | Buttons | Callback action values |
|---|---|---|
YES_NO | Yes · No | "yes" · "no" |
APPROVE_DISMISS | Approve · Dismiss | "approve" · "dismiss" |
CONFIRM | Confirm | "confirm" |
Buttons run in the background — the app does not need to open for the callback to fire.
Example — deployment approval
curl -X POST https://api.notifikations.com/api/v1/YOUR_SECRET \ -H 'Content-Type: application/json' \ -d '{ "title": "Deployment", "message": "Deploy to production?", "category": "YES_NO", "callback_url": "https://your-n8n.com/webhook/abc123" }'
When the user taps Yes, the app sends:
POST https://your-n8n.com/webhook/abc123
Content-Type: application/json
{ "action": "yes" }callback_url is passed through to the APNs payload and never stored by the Notifikations server. The response travels directly from your device to the URL you provided — the Notifikations server is not in the loop. Only use webhooks from sources you trust; tapping an action will reveal your response and its timing to the callback server.
Register a device
POST /api/v1/registerCalled automatically by the iOS app on first launch and when the APNs token changes. Not intended for direct use.
| Field | Type | Description |
|---|---|---|
deviceToken | string required | Hex-encoded APNs device token. |
deviceDigest | string required | SHA-256 hex digest of the device secret. |
userDigest | string required | SHA-256 hex digest of the user secret. |
label | string | Human-readable device name. |
Rotate secrets
POST /api/v1/rotateGenerates a new device webhook URL and immediately invalidates the old one. Called by the app — not intended for direct use.
| Field | Type |
|---|---|
oldDigest | string required |
newDigest | string required |
deviceToken | string required |
Unregister a device
DELETE /api/v1/unregister| Field | Type |
|---|---|
deviceDigest | string required |