- Startseite
- API-Dokumentation
Für Entwickler
PfotenCard API & Webhooks
Verbinde deine Hundeschul-Software mit Website, Newsletter-Tool, Buchhaltung oder Automatisierungen wie Zapier, Make und n8n. Die REST-API spricht JSON, Webhooks melden Ereignisse in Echtzeit und sind signiert.
- Version v1, stabile Datenformen
- Zugriff per API-Key mit einzeln wählbaren Berechtigungen
- Webhooks mit HMAC-SHA256-Signatur und automatischen Wiederholungen
- Verfügbar mit dem Modul „API & Webhooks“ (je nach Paket oder als Zusatzpaket)
Schnellstart
1. In den Einstellungen unter App-Module → API & Webhooks das Modul aktivieren (Admin-Zugang nötig). 2. Einen API-Key mit den benötigten Berechtigungen erstellen – er wird genau einmal angezeigt. 3. Den Key bei jeder Anfrage im Header mitschicken:
curl https://api.pfotencard.de/api/v1/me \
-H "Authorization: Bearer pc_live_…"Die Hundeschule ergibt sich aus dem Key – eine Schul-ID musst du nicht angeben. Keys gehören auf einen Server oder in ein Automatisierungs-Tool, nie in öffentlichen Webseiten-Code. Die API ist für Server-zu-Server-Aufrufe gedacht; Browser-Aufrufe von fremden Domains sind nicht freigegeben.
Grundlagen
Berechtigungen (Scopes)
read:customers– Kunden, Hunde und Level lesenwrite:customers– Kunden anlegen und Kontaktdaten ändernread:appointments– Termine und Buchungen lesenread:transactions– Transaktionen lesen
Seitenweises Abrufen
Listen sind nach ID aufsteigend sortiert und liefern data, has_more und next_cursor. Für die nächste Seite starting_after=<next_cursor> anhängen. limit: 1–100 (Termine, Buchungen, Transaktionen bis 500).
GET https://api.pfotencard.de/api/v1/customers?limit=50&starting_after=1234
{
"data": [ { "id": 1240, "first_name": "Kira", ... } ],
"has_more": true,
"next_cursor": "1289"
}Fehler
Fehler kommen immer im selben Format mit passendem HTTP-Status:
HTTP/1.1 403 Forbidden
{ "error": { "code": "insufficient_scope", "message": "Dem API-Key fehlt die Berechtigung \"read:transactions\"." } }401 unauthorized/key_expired– Key fehlt, ist falsch, widerrufen oder abgelaufen402 subscription_inactive– das PfotenCard-Abo der Schule ist nicht aktiv403 plan_required– das Modul ist im gebuchten Tarif nicht enthalten403 module_disabled– die Schule hat das Modul ausgeschaltet403 insufficient_scope– Berechtigung fehlt400 invalid_parameter,404 not_found,409 already_exists429 rate_limited– mehr als 120 Anfragen pro Minute und Key
Endpunkte
Basis-Adresse: https://api.pfotencard.de/api/v1
| Methode | Pfad | Berechtigung | Beschreibung |
|---|---|---|---|
| GET | /me | – | Schule und Key prüfen (gut zum Testen der Verbindung). |
| GET | /customers | read:customers | Kundinnen und Kunden inkl. Hunde. Filter: email, created_since. |
| GET | /customers/{id} | read:customers | Einzelner Kunde. |
| POST | /customers | write:customers | Kunde anlegen (optional mit Hunden und Einladungs-E-Mail). |
| PATCH | /customers/{id} | write:customers | Name, Telefon und Adresse ändern. |
| GET | /dogs | read:customers | Hunde. Filter: customer_id. |
| GET | /levels | read:customers | Level/Stufen der Schule (für level_id). |
| GET | /appointments | read:appointments | Termine im Zeitraum from/to (Standard: heute + 90 Tage, max. 366 Tage). |
| GET | /appointments/{id} | read:appointments | Termin mit allen Buchungen. |
| GET | /bookings | read:appointments | Buchungen. Filter: customer_id, appointment_id, status, created_since. |
| GET | /transactions | read:transactions | Aufladungen, Abrechnungen, Stornos. Filter: customer_id, from, to. |
Kunde anlegen
Pflicht sind email und ein Name. Mit send_invite: true bekommt der Kunde die gewohnte Einladungs-E-Mail der Schule, um sein Passwort zu setzen. Gibt es die E-Mail schon, antwortet die API mit 409 und der vorhandenen customer_id.
curl -X POST https://api.pfotencard.de/api/v1/customers \
-H "Authorization: Bearer pc_live_…" \
-H "Content-Type: application/json" \
-d '{
"email": "nora@example.de",
"first_name": "Nora",
"last_name": "Neumann",
"phone": "0171 1234567",
"address": { "street": "Hauptstr. 1", "postcode": "50667", "city": "Köln", "country": "DE" },
"dogs": [ { "name": "Fips", "breed": "Mops", "birth_date": "2024-05-01" } ],
"send_invite": true
}'Kunde (Antwortformat)
{
"id": 1240,
"first_name": "Nora",
"last_name": "Neumann",
"name": "Nora Neumann",
"email": "nora@example.de",
"phone": "0171 1234567",
"address": { "street": "Hauptstr. 1", "postcode": "50667", "city": "Köln", "country": "DE" },
"balance": 0,
"level_id": 12,
"is_active": true,
"customer_since": "2026-09-24T09:12:00.000Z",
"dogs": [ { "id": 881, "name": "Fips", "breed": "Mops", "birth_date": "2024-05-01", "level_id": null } ]
}E-Mail-Adresse (Login), Guthaben und Level lassen sich über die API bewusst nicht ändern – das bleibt der App vorbehalten, damit Buchhaltung und Level-Regeln stimmen. Schreibende Aufrufe erscheinen im Änderungsprotokoll der Schule mit der Quelle „API“.
Webhooks
Webhooks richtest du in den Einstellungen ein: https-Adresse eintragen, Ereignisse wählen, fertig. PfotenCard schickt dann für jedes Ereignis ein POST mit JSON an diese Adresse – in der Regel wenige Sekunden nach der Aktion.
| customer.created | Neuer Kunde (App, Registrierung, Import oder API). |
| booking.created | Neue Buchung – auch Wartelistenplatz oder erneute Anmeldung nach Storno. |
| booking.confirmed | Von der Warteliste nachgerückt. |
| booking.cancelled | Buchung storniert oder gelöscht. |
| transaction.created | Neue Transaktion: Aufladung, Abrechnung eines Termins, Storno, Korrektur. |
| level.reached | Kunde oder Hund ist ein Level aufgestiegen. |
| ping | Nur beim Knopf „Test senden“. |
Aufbau einer Nachricht
POST /dein-webhook HTTP/1.1
Content-Type: application/json
PfotenCard-Event: booking.created
PfotenCard-Delivery: 5821
PfotenCard-Signature: t=1790241234,v1=5f2b…c9
{
"id": "evt_10422",
"type": "booking.created",
"created_at": "2026-09-24T09:13:54.000Z",
"school": "deine-hundeschule",
"data": {
"id": 7788, "appointment_id": 912, "customer_id": 1240, "dog_id": 881, "dog_name": "Fips",
"status": "confirmed", "attended": false, "billed": false,
"appointment": { "id": 912, "title": "Welpengruppe", "start": "2026-09-26T08:00:00.000Z", "end": "2026-09-26T09:00:00.000Z" }
}
}data hat dasselbe Format wie die jeweilige API-Ressource und zeigt den Stand zum Zeitpunkt der Zustellung. Ist ein Datensatz bis dahin gelöscht, enthält data den Stand zum Ereigniszeitpunkt und "deleted": true. Nutze id des Ereignisses, um doppelte Zustellungen zu erkennen – in seltenen Fällen kann eine Nachricht mehr als einmal ankommen.
Signatur prüfen
Der Header PfotenCard-Signature enthält einen Zeitstempel t und v1 = HMAC-SHA256(Geheimnis, t + "." + roher Body) als Hex-Wert. Prüfe mit dem unveränderten Body und lehne Nachrichten ab, deren Zeitstempel älter als etwa fünf Minuten ist.
// Node.js / Express
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const SECRET = process.env.PFOTENCARD_WEBHOOK_SECRET; // whsec_…
app.post('/pfotencard-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('PfotenCard-Signature') || '';
const t = /t=(\d+)/.exec(header)?.[1];
const v1 = /v1=([0-9a-f]{64})/.exec(header)?.[1];
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.sendStatus(400);
const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${req.body}`).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(400);
const event = JSON.parse(req.body.toString('utf8'));
// … verarbeiten (idempotent anhand event.id)
res.sendStatus(200);
});# PHP
$payload = file_get_contents('php://input');
preg_match('/t=(\d+),v1=([0-9a-f]{64})/', $_SERVER['HTTP_PFOTENCARD_SIGNATURE'] ?? '', $m);
$expected = hash_hmac('sha256', $m[1] . '.' . $payload, getenv('PFOTENCARD_WEBHOOK_SECRET'));
if (!$m || !hash_equals($expected, $m[2]) || abs(time() - (int)$m[1]) > 300) { http_response_code(400); exit; }
$event = json_decode($payload, true);Antwort, Wiederholungen, Pausieren
Antworte innerhalb von 8 Sekunden mit einem Status 2xx. Weiterleitungen werden nicht verfolgt. Schlägt eine Zustellung fehl, versucht PfotenCard es nach 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden, 6 Stunden und 24 Stunden erneut. Jeder Versuch steht im Protokoll in den Einstellungen und lässt sich dort erneut auslösen. Kommen zehn Ereignisse in Folge nicht an, wird der Webhook pausiert und die Admins der Schule werden benachrichtigt. Protokolle werden 30 Tage aufbewahrt.
Aus Sicherheitsgründen sind nur öffentliche https-Adressen erlaubt – keine internen Netze, kein localhost.
Tarif und Datenschutz
API & Webhooks sind je nach PfotenCard-Paket enthalten oder als Zusatzpaket buchbar. Das wird bei jeder Anfrage geprüft: Endet der Tarif oder das Abo, pausieren Keys und Webhooks automatisch und funktionieren nach erneuter Buchung ohne Neueinrichtung weiter.
Über die API fließen personenbezogene Daten der Kundinnen und Kunden an das angebundene Programm. Die Hundeschule ist dafür verantwortlich, dass es datenschutzkonform eingesetzt wird (z. B. Auftragsverarbeitungsvertrag mit dem Anbieter). Keys lassen sich jederzeit einzeln widerrufen.
Noch keine PfotenCard?
Kunden, Hunde, Termine und Guthaben an einem Ort – und mit der API offen für deine anderen Werkzeuge. Bewirb dich für den Beta-Zugang.