← PushPig
API-Referenz
Alle Endpunkte, Parameter, Scopes und Fehlercodes der PushPig-REST-API.
PushPig API
Basis-URL: https://pushpig.de
Authentifizierung
Die API unterstützt zwei Authentifizierungsmethoden:
1. Session-Cookie (Browser)
Nach dem Login setzt der Server automatisch ein pp_session-Cookie. Alle Requests aus dem Browser werden damit automatisch authentifiziert.
2. API-Schlüssel (Bearer Token)
Für Skripte, Automationen und externe Integrationen. Der Schlüssel wird im Authorization-Header mitgeschickt:
Authorization: Bearer pp_<schlüssel>
API-Schlüssel können in den Einstellungen unter API-Schlüssel erstellt werden. Der Schlüssel wird nur einmal beim Erstellen angezeigt, bitte sofort sichern.
Scopes
Jeder Key trägt Scopes, die festlegen welche Aktionen er ausführen darf:
| Scope |
Bedeutung |
push:send |
Push-Nachrichten senden (an alle Channels mit Schreibrecht) |
push:send:<channel_id> |
Nur in einen bestimmten Channel senden |
channels:read |
Channels und Events lesen |
channels:read:<channel_id> |
Nur einen bestimmten Channel lesen |
channels:create |
Neue Channels anlegen |
channels:update |
Channels bearbeiten (nur eigene) |
channels:update:<channel_id> |
Nur einen bestimmten Channel bearbeiten |
channels:delete |
Channels löschen (nur eigene) |
channels:delete:<channel_id> |
Nur einen bestimmten Channel löschen |
Beim Anlegen eines Keys ist mindestens ein Scope Pflicht. Ein Key ohne Scopes hat keinerlei Zugriff, jeder Request wird mit HTTP 403 abgewiesen.
Ein Scope ohne Channel-ID schließt alle Varianten mit ID ein, aber nicht umgekehrt:
push:send erlaubt das Senden in jeden Channel mit Schreibrecht.
push:send:42 erlaubt das Senden ausschließlich in Channel 42. Weder Channel 99 noch „alle Channels“ sind damit abgedeckt.
Dasselbe gilt für channels:read, channels:update und channels:delete.
Unabhängig vom Scope bleiben die normalen Rechte bestehen: bearbeiten und löschen darf nur, wem der Channel gehört, senden nur, wer Schreibrecht hat, und die Plan-Limits (Free: 2 Channels) gelten auch über die API.
Push-Nachrichten senden
POST /api/push/send
Sendet eine Nachricht an einen Kanal. Alle Abonnenten des Kanals erhalten die Nachricht in Echtzeit.
Authentifizierung: Session-Cookie oder Bearer Token (Scope push:send)
Request:
{
"channel": "general",
"title": "Optionaler Titel",
"body": "Nachrichtentext",
"send_at": "2026-12-24T18:00:00",
"urgent": false,
"actions": [
{ "title": "Öffnen", "url": "https://example.com" }
],
"payload": { "custom": "data" }
}
| Feld |
Typ |
Pflicht |
Beschreibung |
channel |
string |
✓* |
Slug des Kanals |
channel_id |
integer |
✓* |
Alternative zu channel |
body |
string |
✓ |
Nachrichtentext (max. 4096 Zeichen) |
title |
string |
– |
Optionaler Betreff (max. 255 Zeichen) |
actions |
array |
– |
Bis zu 2 Aktionsbuttons mit title + url |
send_at |
string |
– |
ISO-8601-Zeitstempel für verzögerten Versand (mind. 30 s in der Zukunft) |
urgent |
boolean |
– |
Wenn true, umgeht die Push die Quiet-Hours der Empfänger |
payload |
object |
– |
Beliebige JSON-Nutzdaten |
* Entweder channel oder channel_id ist erforderlich.
Response (200):
{
"success": true,
"event_id": 42,
"fcm_sent": 3,
"webpush_sent": 1,
"email_sent": 2,
"deferred": 0
}
deferred ist die Anzahl Empfänger, deren Quiet-Hours gerade aktiv sind und denen die Push später automatisch zugestellt wird.
Beispiele:
curl -X POST https://pushpig.de/api/push/send \
-H "Authorization: Bearer pp_deinschluessel" \
-H "Content-Type: application/json" \
-d '{"channel": "server", "body": "Server ist online!"}'
await fetch("https://pushpig.de/api/push/send", {
method: "POST",
headers: {
"Authorization": "Bearer pp_deinschluessel",
"Content-Type": "application/json"
},
body: JSON.stringify({ channel: "general", title: "Alert", body: "Festplatte fast voll!" })
});
import requests
requests.post(
"https://pushpig.de/api/push/send",
headers={"Authorization": "Bearer pp_deinschluessel"},
json={"channel": "general", "title": "Alert", "body": "Festplatte fast voll!"}
)
<?php
$ch = curl_init("https://pushpig.de/api/push/send");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer pp_deinschluessel",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"channel" => "general",
"title" => "Alert",
"body" => "Festplatte fast voll!",
]),
]);
curl_exec($ch);
rest_command:
pushpig_notify:
url: https://pushpig.de/api/push/send
method: POST
headers:
Authorization: "Bearer pp_deinschluessel"
Content-Type: application/json
payload: '{"channel": "system", "title": "{{ title }}", "body": "{{ message }}"}'
POST /api/push/test
Sendet eine Test-Push nur an die eigenen Geräte (FCM-Tokens und Web-Push-Subscriptions des aktuellen Users). Praktisch zum Verifizieren des eigenen Push-Setups.
Authentifizierung: Session-Cookie
Request:
{ "channel_id": 1 }
Response (200):
{
"success": true,
"fcm_sent": 1,
"webpush_sent": 1,
"total": 2
}
Vorlagen (Templates)
Wiederkehrende Nachrichten als Vorlagen speichern.
GET /api/push/templates/list
Liefert alle für den User sichtbaren Vorlagen (eigene + channel-shared mit Schreibrecht).
Response (200):
{
"templates": [
{
"id": 1,
"name": "Deploy fertig",
"title": "Deploy {version}",
"body": "{env} ist live.",
"channel_id": null,
"channel_slug": null,
"is_owner": true,
"author_name": "alice",
"updated_at": "2026-05-15 20:00:00"
}
]
}
POST /api/push/templates/create
Request:
{
"name": "Deploy fertig",
"title": "Deploy {version}",
"body": "{env} ist live.",
"channel_id": null,
"actions": []
}
channel_id weglassen oder null → persönliche Vorlage. Mit channel_id → geteilt mit allen Schreibern des Channels.
POST /api/push/templates/update
Body: { "id": 1, "name": "...", "body": "...", ... }
POST /api/push/templates/delete
Body: { "id": 1 }
Bulk-Send
Massenversand mit pro-Empfänger-Variablen.
POST /api/channels/bulk/create
Request:
{
"channel_id": 1,
"title_template": "Hallo {name}!",
"body_template": "Du hast {prize} gewonnen.",
"csv": "username,name,prize\nalice,Alice,100 EUR\nbob,Bob,50 EUR"
}
Spalte username ist Pflicht. Weitere Spalten werden zu Platzhaltern. Empfänger, die den Channel nicht abonniert haben, werden übersprungen.
Response (201):
{
"success": true,
"job_id": 42,
"total": 2,
"resolved": 2,
"skipped": 0
}
Der Job wird asynchron im Hintergrund verarbeitet. Status abrufen:
GET /api/channels/bulk/status?job_id=42
GET /api/channels/bulk/list?channel_id=1
POST /api/channels/bulk/cancel – { "job_id": 42 }
GET /api/channels/bulk/recipients?job_id=42&status=skipped
Limits: max. 5000 Zeilen pro CSV, max. 5 Bulk-Jobs pro User pro Stunde.
Quiet Hours
POST /api/channels/quiet-hours
Setzt die Nicht-Stören-Zeitspanne des aktuellen Users für einen Channel.
Request:
{
"channel_id": 1,
"quiet_start": "22:00",
"quiet_end": "07:00"
}
Beide null → Quiet-Hours entfernen. Format HH:MM. Wenn quiet_end < quiet_start, erstreckt sich das Fenster über Mitternacht (z. B. 22:00 → 07:00).
Die Zeitzone wird aus dem User-Profil gelesen.
POST /api/auth/profile/timezone
Setzt die IANA-Zeitzone des Users:
{ "timezone": "Europe/Berlin" }
null → Server-Default.
Webhooks
Channel-Owner können externe URLs hinterlegen, die bei bestimmten Events angerufen werden.
Unterstützte Events
| Event |
Wann |
push.sent |
Push wurde an mindestens einen Empfänger zugestellt |
push.failed |
Push hat keinen Empfänger erreicht (keine Subscriber / alle Tokens expired) |
channel.subscribed |
Neuer Subscriber im Channel |
channel.unsubscribed |
Subscriber hat sich abgemeldet |
bulk.completed |
Bulk-Job abgeschlossen |
Signatur-Verifikation
Jeder Webhook-Request enthält folgende Header:
Content-Type: application/json
X-PushPig-Event: push.sent
X-PushPig-Delivery: 8a4f9b21c0d3...
X-PushPig-Idempotency-Key: 8a4f9b21c0d3...
X-PushPig-Timestamp: 1747333200
X-PushPig-Signature: sha256=<HMAC-SHA256("<timestamp>.<raw_body>", webhook_secret)>
Signiert wird "<X-PushPig-Timestamp>.<raw_body>" (Stripe-Stil), nicht der Body
allein. Der Zeitstempel ist pro Sende-Versuch frisch.
Replay-Schutz: Empfänger sollten Requests abweisen, deren X-PushPig-Timestamp
mehr als 300 s von der eigenen Uhr abweicht, so lassen sich abgefangene Requests
nicht beliebig später erneut einspielen. Echte Retries tragen einen neuen, gültigen
Zeitstempel und passieren weiterhin.
Idempotenz: X-PushPig-Delivery (= X-PushPig-Idempotency-Key) ist über alle
Retry-Versuche desselben Events identisch. Empfänger sollten diesen Wert speichern
und bereits verarbeitete Deliveries verwerfen, um Doppelverarbeitung auszuschließen.
Empfänger-Verifikation (Bash):
# $TS = X-PushPig-Timestamp, $BODY = roher Request-Body, $SIG = X-PushPig-Signature
# 1) Frische prüfen (Replay-Schutz)
now=$(date +%s)
(( now - TS < 300 && TS - now < 300 )) || exit 1
# 2) Signatur über "<timestamp>.<body>" nachrechnen
expected=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
[[ "sha256=$expected" == "$SIG" ]] || exit 1
Payload-Beispiel
{
"event": "push.sent",
"channel_id": 42,
"timestamp": "2026-05-15T20:00:00+00:00",
"data": {
"event_id": 1234,
"channel_slug": "deploys",
"title": "Deploy erfolgreich",
"body": "Version 2.4.1 ist live.",
"fcm_sent": 3,
"webpush_sent": 1,
"email_sent": 0
}
}
Retry-Politik
Fehlgeschlagene Zustellungen (HTTP ≠ 2xx oder Timeout) werden bis zu 5× wiederholt mit Backoff 30 s → 5 min → 30 min → 1 h. HTTP-Timeout: 8 s. Jeder Retry trägt dieselbe X-PushPig-Delivery-ID (Idempotenz), aber einen frischen X-PushPig-Timestamp mit passender Signatur.
Endpoints zur Verwaltung
POST /api/channels/webhooks/create – { channel_id, url, events: [...] }, gibt einmalig das secret zurück
GET /api/channels/webhooks/list?channel_id=1
POST /api/channels/webhooks/delete – { id }
POST /api/channels/webhooks/test – { id }, synchroner Test-Aufruf
Channels
GET /api/channels
Listet alle Channels, in denen der User abonniert ist. Antwort enthält pro Channel auch dessen quiet_start/quiet_end für den aktuellen User.
Authentifizierung: Session-Cookie oder Bearer Token (Scope channels:read)
Response (200):
{
"channels": [
{
"id": 1,
"name": "Allgemein",
"slug": "general",
"description": "Allgemeine Benachrichtigungen",
"is_public": true,
"is_owner": false,
"can_write": false,
"subscribed": true,
"email_notify": false,
"push_notify": true,
"quiet_start": "22:00",
"quiet_end": "07:00",
"subscriber_count": 3,
"unread_count": 0
}
]
}
GET /api/channels/events?channel_id=1
Listet die letzten Events des Channels. Antwort enthält pro Event die Delivery-Counter fcm_sent, webpush_sent, email_sent.
Authentifizierung: Session-Cookie oder Bearer Token (Scope channels:read oder channels:read:<channel_id>)
POST /api/channels/create
Legt einen Channel an. Der Aufrufer wird automatisch Owner und Abonnent. Der slug wird aus dem Namen abgeleitet und muss systemweit eindeutig sein.
Authentifizierung: Session-Cookie oder Bearer Token (Scope channels:create, bestätigte E-Mail-Adresse erforderlich)
Request:
{ "name": "Mein Kanal", "description": "...", "is_public": true }
| Feld |
Typ |
Pflicht |
Bedeutung |
name |
string |
ja |
Anzeigename, max. 100 Zeichen |
description |
string |
nein |
Beschreibung |
is_public |
bool |
nein |
Default true, öffentlich auffindbar |
Response (201):
{ "success": true, "channel": { "id": 7, "name": "Mein Kanal", "slug": "mein-kanal" } }
Ist das Channel-Limit des Plans erreicht, antwortet der Endpoint mit HTTP 402.
POST /api/channels/update
Ändert einen Channel. Nur der Owner darf das. Es werden nur die mitgeschickten Felder geändert, mindestens eines ist erforderlich.
Authentifizierung: Session-Cookie oder Bearer Token (Scope channels:update oder channels:update:<channel_id>)
Request:
{ "id": 7, "name": "Neuer Name", "description": "...", "is_public": false }
| Feld |
Typ |
Pflicht |
Bedeutung |
id |
int |
ja |
Channel-ID |
name |
string |
nein |
Neuer Name, max. 100 Zeichen |
description |
string|null |
nein |
Neue Beschreibung, max. 500 Zeichen, null löscht sie |
is_public |
bool |
nein |
Sichtbarkeit |
Der slug bleibt beim Umbenennen unverändert, bestehende Integrationen brechen also nicht.
Response (200):
{ "success": true }
POST /api/channels/delete
Löscht einen Channel endgültig, samt Abos und Events. Nur der Owner darf das, die Aktion ist nicht umkehrbar.
Authentifizierung: Session-Cookie oder Bearer Token (Scope channels:delete oder channels:delete:<channel_id>)
Request:
{ "id": 7 }
Response (200):
{ "success": true, "message": "Kanal gelöscht." }
POST /api/channels/subscribe
Abonniert oder kündigt:
{ "channel_id": 1 }
Zum Abbestellen _method: "DELETE" hinzufügen.
Einladungs-Links
POST /api/channels/invite-link/create – { channel_id }, erzeugt einen einmalig nutzbaren Token (7 Tage gültig) und gibt die Invite-URL zurück
GET /api/channels/invite-link/list?channel_id=1 – offene Links
POST /api/channels/invite-link/revoke – { id }, Link widerrufen
POST /api/channels/join – { token }, Einladung einlösen
SSE – Echtzeit-Empfang
GET /api/sse/stream
Öffnet einen Server-Sent Events Stream. Der Browser empfängt Push-Nachrichten in Echtzeit, solange die Verbindung offen ist.
Authentifizierung: Session-Cookie
Dieser Endpoint ist nur für Browser-Clients gedacht. Für externe Integrationen genügt das Senden über /api/push/send.
Event-Format:
data: {"type":"message","channel":"general","title":"Hallo","body":"Testnachricht","created_at":"2026-04-24T10:00:00Z"}
Fehler
Alle Fehler werden als JSON zurückgegeben:
{ "error": "Fehlerbeschreibung" }
| HTTP-Code |
Bedeutung |
| 400 |
Ungültige Anfrage (fehlende/falsche Parameter) |
| 401 |
Nicht authentifiziert |
| 403 |
Keine Berechtigung (z. B. fehlender Scope oder kein Schreibrecht) |
| 404 |
Ressource nicht gefunden |
| 405 |
HTTP-Methode nicht erlaubt |
| 409 |
Konflikt (z. B. Invite bereits eingelöst) |
| 410 |
Abgelaufen (Invite oder Token) |
| 429 |
Rate-Limit überschritten |
| 500 |
Serverfehler |
Hilfe & API ·
Preise ·
Kontakt ·
Impressum ·
Datenschutz