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 lesen
  • write:customers – Kunden anlegen und Kontaktdaten ändern
  • read:appointments – Termine und Buchungen lesen
  • read: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 abgelaufen
  • 402 subscription_inactive – das PfotenCard-Abo der Schule ist nicht aktiv
  • 403 plan_required – das Modul ist im gebuchten Tarif nicht enthalten
  • 403 module_disabled – die Schule hat das Modul ausgeschaltet
  • 403 insufficient_scope – Berechtigung fehlt
  • 400 invalid_parameter, 404 not_found, 409 already_exists
  • 429 rate_limited – mehr als 120 Anfragen pro Minute und Key

Endpunkte

Basis-Adresse: https://api.pfotencard.de/api/v1

MethodePfadBerechtigungBeschreibung
GET/me–Schule und Key prüfen (gut zum Testen der Verbindung).
GET/customersread:customersKundinnen und Kunden inkl. Hunde. Filter: email, created_since.
GET/customers/{id}read:customersEinzelner Kunde.
POST/customerswrite:customersKunde anlegen (optional mit Hunden und Einladungs-E-Mail).
PATCH/customers/{id}write:customersName, Telefon und Adresse ändern.
GET/dogsread:customersHunde. Filter: customer_id.
GET/levelsread:customersLevel/Stufen der Schule (für level_id).
GET/appointmentsread:appointmentsTermine im Zeitraum from/to (Standard: heute + 90 Tage, max. 366 Tage).
GET/appointments/{id}read:appointmentsTermin mit allen Buchungen.
GET/bookingsread:appointmentsBuchungen. Filter: customer_id, appointment_id, status, created_since.
GET/transactionsread:transactionsAufladungen, 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.createdNeuer Kunde (App, Registrierung, Import oder API).
booking.createdNeue Buchung – auch Wartelistenplatz oder erneute Anmeldung nach Storno.
booking.confirmedVon der Warteliste nachgerückt.
booking.cancelledBuchung storniert oder gelöscht.
transaction.createdNeue Transaktion: Aufladung, Abrechnung eines Termins, Storno, Korrektur.
level.reachedKunde oder Hund ist ein Level aufgestiegen.
pingNur 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.