Einen Qvard-Webhook verifizieren

Ein kurzer Leitfaden zum Header X-Qvard-Signature: was er ist und wie man ihn mit ein paar Zeilen Code verifiziert.

Was ist eine Signatur

Wenn sich der Status einer Zahlung ändert, sendet Qvard eine Benachrichtigung (Webhook) an die Adresse in Ihren Shop-Einstellungen. Jeder kann eine Anfrage an diese Adresse senden, daher benötigen Sie eine Möglichkeit, eine echte Benachrichtigung von einer gefälschten zu unterscheiden.

Die Signatur ist dieser Beweis: ein kurzer Code, den Qvard aus der Nachricht und Ihrem geheimen Schlüssel berechnet. Nur Qvard und Sie kennen den Schlüssel, daher kann nur Qvard den richtigen Code erzeugen.

Was Sie erhalten

Jeder Webhook trägt einen zusätzlichen Header und einen kleinen JSON-Body:

X-Qvard-Signature: t=1760950000,v1=3f2a…c91e {"id":"order_1024","status":"paid"}

t ist die Zeit, in der die Nachricht gesendet wurde (Unix, Sekunden). v1 ist der Code. Für 24 Stunden nach der Rotation Ihres geheimen Schlüssels trägt der Header zwei v1-Werte: einen für den neuen Schlüssel und einen für den alten.

Die Felder id und status bleiben wie bisher. Dem gleichen signierten Body werden vier Betragsfelder hinzugefügt:

  • amount und currency: der Betrag und seine Währung, so wie Sie sie in der Anfrage gesendet haben.
  • amountUsd: der Buchungsbetrag in USD.
  • rate: Währungseinheiten für 1 USD zum Zeitpunkt der Zahlungserstellung.
  • Bestehende Integrationen funktionieren weiter: Sie lesen id und status und ignorieren die neuen Felder. Die Signatur wird wie bisher über denselben Body geprüft.

Wie man es überprüft

  1. 1
    Nehmen Sie den Request-Body genau wie erhalten: Rohtext, vor jeglichem JSON-Parsing.
  2. 2
    Lesen Sie t und jeden v1 aus dem Header X-Qvard-Signature.
  3. 3
    Berechnen Sie HMAC-SHA256 des Textes t + "." + Body mit Ihrem geheimen Schlüssel (qvard_sk_…) und schreiben Sie ihn als Kleinbuchstaben-Hexadezimal.
  4. 4
    Wenn Ihr Ergebnis einem beliebigen v1 entspricht (vergleichen Sie in konstanter Zeit), ist die Nachricht echt.
  5. 5
    Überprüfen Sie, dass t nicht älter als 5 Minuten ist, damit eine alte Nachricht nicht erneut gespielt werden kann.
  6. 6
    Verwenden Sie erst dann die Daten und bestätigen Sie den gezahlten Betrag für diese Bestellung in Ihrer eigenen Datenbank.

Fertiger Code

const crypto = require('crypto');

function verifyQvardSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = header.split(',').map((p) => p.trim());
  const t = Number((parts.find((p) => p.startsWith('t=')) || '').slice(2));
  const sigs = parts.filter((p) => p.startsWith('v1=')).map((p) => p.slice(3));
  if (!t || sigs.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  return sigs.some((s) => {
    const got = Buffer.from(s, 'hex');
    return got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
}

// Use the RAW body: express.raw, not express.json
app.post('/qvard-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyQvardSignature(raw, req.get('X-Qvard-Signature') || '', process.env.QVARD_SECRET_KEY)) {
    return res.sendStatus(400);
  }
  const { id, status } = JSON.parse(raw);
  // Confirm the paid amount for order `id` in your own database before fulfilling it.
  res.sendStatus(200);
});

Für 24 Stunden nach der Rotation Ihres geheimen Schlüssels trägt der Header zwei v1-Werte: akzeptieren Sie den Webhook, wenn einer von ihnen übereinstimmt.

Häufige Fehler

  • Das Parsing von JSON und dessen erneute Serialisierung ändert den Text. Verwenden Sie immer den Rohtext.
  • Verwendung des öffentlichen Schlüssels (qvard_pk_…) statt des geheimen Schlüssels.
  • Eine desynchronisierte Server-Uhr führt dazu, dass die 5-Minuten-Prüfung fehlschlägt. Halten Sie sie synchronisiert.
  • Vergleich mit == statt mit konstantem Zeit-Vergleich.

Wo Sie Ihren geheimen Schlüssel finden

Im Dashboard: Ihr Shop, Einstellungen, API-Schlüssel. Speichern Sie ihn nur auf Ihrem Server. Falls er auslaufen sollte, rotieren Sie ihn dort: Der alte Schlüssel läuft 24 Stunden lang für Zahlungen (nicht für Auszahlungen).

Dashboard öffnen

API-Referenz

Ältere Schlüssel

Wenn Ihr Shop noch einen Schlüssel im alten Format hat (ohne qvard_sk_-Präfix), enthalten Webhooks auch einen x-api-key-Header mit diesem Schlüssel. Nach der Rotation verschwindet dieser Header. Wechseln Sie zur Signaturprüfung.

Möchten Sie das Zahlungsfenster ausprobieren?

Demo öffnen