PricelyDocs

Webhooks

Verbinden Sie Pricely mit Zapier, Make oder Ihrem eigenen Code. Pricely sendet bei wichtigen Ereignissen einen signierten JSON-POST an Ihre URL und wiederholt ihn, wenn Ihr Endpunkt kurz nicht erreichbar ist.

Business8 Min. Lesezeit

Ein Webhook ist eine URL, die Pricely aufruft, wenn in Ihrem Workspace etwas passiert. Richten Sie ihn auf ein Zapier- oder Make-Szenario, um ein CRM zu aktualisieren, in einem Chat-Kanal zu posten oder eine Zeile in einer Tabelle anzulegen – oder auf Ihren eigenen Server. Jede Anfrage ist signiert, sodass Ihr Code nachweisen kann, dass sie von Pricely stammt.

Der Webhook-Dialog mit Endpunkt-URL, Signatur-Secret, Ereignisliste und der Schaltfläche „Test senden“
Der Webhook-Dialog, geöffnet über die Seite „Automatisierung“.

Webhook einrichten

  1. Automatisierung öffnen

    Wählen Sie Automatisierungen in der Seitenleiste und suchen Sie die Karte Webhooks.

  2. Endpunkt hinzufügen

    Klicken Sie auf Webhook konfigurieren und fügen Sie Ihre Endpunkt-URL ein, zum Beispiel eine Catch Hook-URL von Zapier oder eine Custom webhook-URL von Make. Es muss eine öffentliche https://-Adresse sein.

  3. Signatur-Secret kopieren

    Der Dialog zeigt ein Signatur-Secret, das mit whsec_ beginnt. Klicken Sie auf Kopieren und speichern Sie es in der Konfiguration Ihres Endpunkts. Sie brauchen es, um Signaturen zu prüfen.

  4. Test senden

    Klicken Sie auf Test senden. Pricely sendet ein signiertes ping-Ereignis und zeigt, was Ihr Endpunkt geantwortet hat, zum Beispiel Zugestellt. Ihr Endpunkt hat mit 200 geantwortet.

  5. Speichern

    Klicken Sie auf Webhook speichern. Die Karte zeigt danach die Ziel-URL, und die Schaltfläche heißt Webhook aktualisieren.

Jeder Workspace hat eine Webhook-URL. Um sie zu ändern, öffnen Sie denselben Dialog und speichern eine neue URL. Um keine Ereignisse mehr zu senden, klicken Sie auf Entfernen.

Ereignisse

EreignisGesendet, wenn
quote.acceptedEin Angebot angenommen wird: Der Kunde unterschreibt es über den Angebotslink, oder jemand aus Ihrem Team nutzt Annehmen in der App.
invoice.paidEine Rechnung auf beliebigem Weg vollständig bezahlt wird: per Kartenzahlung über Stripe, durch eine selbst erfasste Zahlung, Als vollständig bezahlt markieren, eine Sammelaktion oder eine Gutschrift, die den offenen Betrag ausgleicht.
invoice.overdueEine unbezahlte Rechnung ihr Fälligkeitsdatum überschreitet. Wird pro Rechnung einmal gesendet, durch die tägliche Prüfung um 07:00 UTC.
pingSie auf Test senden klicken.

Rechnungen, die bereits bezahlt angelegt werden, etwa durch einen Import, senden kein invoice.paid. Auch eine Teilzahlung sendet es nicht; das Ereignis wird ausgelöst, wenn der offene Betrag null erreicht.

Format der Anfrage

Jedes Ereignis ist ein POST mit JSON-Body und diesen Headern:

HeaderWert
Content-Typeapplication/json
User-AgentPricely-Webhooks/1.0
X-Pricely-EventDer Name des Ereignisses, zum Beispiel invoice.paid.
X-Pricely-DeliveryEine eindeutige ID für diese Zustellung, etwa evt_3f9a…. Wiederholungen behalten dieselbe ID.
X-Pricely-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>. Siehe Signaturen prüfen.

Der Body hat immer dieselben vier Schlüssel: id (die Zustell-ID, identisch mit X-Pricely-Delivery), event, data und timestamp.

Anfrage-Header
POST /pricely-webhook HTTP/1.1
Content-Type: application/json
User-Agent: Pricely-Webhooks/1.0
X-Pricely-Event: invoice.paid
X-Pricely-Delivery: evt_3f9a6c0e1b7d4a52a8e2f1c9d0b4e7a6
X-Pricely-Signature: t=1793372645,v1=5d1c0f…
quote.accepted
{
  "id": "evt_3f9a6c0e1b7d4a52a8e2f1c9d0b4e7a6",
  "event": "quote.accepted",
  "data": {
    "id": "q_8f2c1a",
    "number": "QT-0042",
    "clientId": "c_71b0d2",
    "clientName": "Northwind Studio",
    "clientEmail": "ops@example.com",
    "total": 4800,
    "currency": "USD",
    "acceptedAt": "2026-10-30T15:04:05.000Z"
  },
  "timestamp": "2026-10-30T15:04:05.120Z"
}
invoice.paid
{
  "id": "evt_9b2e4d7a0c5f4e1b9a3d6c8e2f7a1b40",
  "event": "invoice.paid",
  "data": {
    "id": "inv_51c9e0",
    "number": "INV-0042",
    "clientId": "c_71b0d2",
    "clientName": "Northwind Studio",
    "clientEmail": "ops@example.com",
    "total": 4800,
    "amountPaid": 4800,
    "balanceDue": 0,
    "currency": "USD",
    "dueDate": "2026-11-13T12:00:00.000Z",
    "paidAt": "2026-10-30T15:04:05.000Z"
  },
  "timestamp": "2026-10-30T15:04:05.120Z"
}

invoice.overdue enthält dieselben data-Felder wie invoice.paid, mit dem noch offenen Betrag in balanceDue und paidAt auf null.

FeldTypHinweise
idstringDie ID des Angebots oder der Rechnung in Pricely.
numberstringDie Dokumentnummer, die Ihr Kunde sieht.
clientId, clientName, clientEmailstringLeere Strings, wenn dem Dokument kein Kunde zugeordnet ist oder der Kunde keine E-Mail-Adresse hat.
total, amountPaid, balanceDuenumberBeträge in der Haupteinheit, zum Beispiel 4800 für $4,800.00.
currencystringDer dreistellige Währungscode des Dokuments.
acceptedAt, dueDate, paidAt, timestampstring oder nullISO-8601-Zeitangaben in UTC.

Das Testereignis verwendet dieselben Header und dieselbe Signatur. Sein Body sieht so aus:

ping
{
  "id": "evt_0c4d2a9e7f1b4c3d8e6a5b2f9d1c7e30",
  "event": "ping",
  "data": {
    "message": "Test event from Pricely",
    "orgId": "org_2f81c4"
  },
  "timestamp": "2026-10-30T15:04:05.000Z"
}

Signaturen prüfen

Prüfen Sie die Signatur, bevor Sie einer Anfrage vertrauen. X-Pricely-Signature besteht aus zwei Teilen: t, der Unix-Zeit in Sekunden, zu der die Anfrage signiert wurde, und v1, einem hexadezimalen HMAC-SHA256 der Zeichenfolge t + . + roher Request-Body, mit Ihrem Signatur-Secret als Schlüssel.

  1. Rohen Body lesen

    Verwenden Sie genau die empfangenen Bytes. Wenn Sie das JSON parsen und neu serialisieren, ändern sich die Bytes, und die Signatur stimmt nicht mehr.

  2. Header zerlegen

    Teilen Sie ihn an den Kommas und jeden Teil am ersten =, um t und v1 zu erhalten.

  3. HMAC neu berechnen

    Berechnen Sie den HMAC-SHA256 von t.body mit Ihrem whsec_…-Secret und vergleichen Sie ihn in konstanter Zeit mit v1.

  4. Zeit prüfen

    Lehnen Sie Anfragen ab, bei denen t mehr als fünf Minuten von der Uhr Ihres Servers abweicht. So lassen sich alte Anfragen nicht erneut einspielen.

Node.js (Express)
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.PRICELY_WEBHOOK_SECRET; // whsec_…
const TOLERANCE_SECONDS = 300;

function verifyPricelySignature(header, rawBody, secret) {
  const parts = {};
  for (const piece of String(header || "").split(",")) {
    const i = piece.indexOf("=");
    if (i > 0) parts[piece.slice(0, i).trim()] = piece.slice(i + 1).trim();
  }
  const t = Number(parts.t);
  if (!Number.isFinite(t) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest();
  const given = Buffer.from(parts.v1, "hex");
  return given.length === expected.length && crypto.timingSafeEqual(expected, given);
}

// express.raw keeps the exact bytes Pricely signed.
app.post("/pricely-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const rawBody = req.body.toString("utf8");
  if (!verifyPricelySignature(req.get("X-Pricely-Signature"), rawBody, SECRET)) {
    return res.status(400).send("Invalid signature");
  }
  const delivery = JSON.parse(rawBody);
  res.sendStatus(200); // answer first, then do the work

  // Skip delivery.id if you've already handled it (retries reuse it).
  if (delivery.event === "invoice.paid") {
    console.log(`${delivery.data.number} paid: ${delivery.data.amountPaid} ${delivery.data.currency}`);
  }
});

app.listen(3000);
Python
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 300

def verify_pricely_signature(header: str, raw_body: bytes, secret: str) -> bool:
    parts = {}
    for piece in (header or "").split(","):
        key, sep, value = piece.partition("=")
        if sep:
            parts[key.strip()] = value.strip()
    try:
        t = int(parts["t"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > TOLERANCE_SECONDS or "v1" not in parts:
        return False

    signed = parts["t"].encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

# Flask: verify_pricely_signature(request.headers.get("X-Pricely-Signature"),
#                                 request.get_data(), SECRET)

Zustellung und Wiederholungen

  • Nur HTTPS. Pricely stellt nur an öffentliche https://-URLs zu. Lokale und private Netzwerkadressen wie localhost, *.local oder 192.168.x.x sind gesperrt.
  • Keine Weiterleitungen. Leitet Ihre URL woandershin weiter, schlägt die Zustellung fehl. Verwenden Sie die endgültige URL.
  • Antworten Sie innerhalb von 10 Sekunden mit 2xx. Jeder 2xx-Status bestätigt den Empfang. Antworten Sie zuerst und erledigen Sie langsame Arbeit danach.
  • Automatische Wiederholungen. Pricely unternimmt insgesamt bis zu drei Versuche und wartet dazwischen 1 Sekunde, dann 2 Sekunden. Jeder Versuch wird mit einem neuen t erneut signiert.
Ihr Endpunkt …Was Pricely tut
antwortet mit 2xxZugestellt. Keine Wiederholung.
ist nicht erreichbar oder braucht länger als 10 SekundenWiederholt.
antwortet mit 408, 429 oder 5xxWiederholt.
antwortet mit einem anderen 4xx, etwa 400 oder 401Bricht ab. Der Fehler ist endgültig.

Die Schaltfläche Test senden unternimmt einen einzigen Versuch ohne Wiederholung, damit Sie das Ergebnis sofort sehen. Der Dialog zeigt außerdem die Letzte Zustellung: das Ereignis, ob es zugestellt wurde oder fehlschlug, den Statuscode, den Zeitpunkt und die Anzahl der Versuche.

Fehlerbehebung

ProblemWas Sie prüfen sollten
Verwenden Sie eine öffentliche https://-URL. Lokale und private Netzwerkadressen sind gesperrt.Die URL zeigt auf Ihren eigenen Rechner oder ein privates Netzwerk. Verwenden Sie eine öffentliche HTTPS-URL oder einen Tunnel, der Ihnen eine bereitstellt.
Ihr Endpunkt hat mit 401 geantwortet. (oder einem anderen Code)Pricely hat Ihren Endpunkt erreicht, aber er hat die Anfrage abgelehnt. Alles außerhalb von 200–299 gilt als Fehler. Prüfen Sie die Logs Ihres Endpunkts.
Endpunkt nicht erreichbar: Keine Antwort innerhalb von 10 sIhr Endpunkt ist zu langsam. Antworten Sie zuerst mit 2xx und erledigen Sie dann die Arbeit.
Endpunkt nicht erreichbar (aus einem anderen Grund)Der Host hat nicht geantwortet, sein TLS-Zertifikat ist ungültig, oder die URL leitet weiter. Prüfen Sie, ob sich die URL im Browser öffnet und ihre endgültige Adresse ist.
Die Signatur stimmt nichtPrüfen Sie gegen den rohen Body, nicht gegen neu serialisiertes JSON, und verwenden Sie das Secret, das aktuell im Dialog angezeigt wird. Haben Sie auf „Neues Secret“ geklickt, speichern Sie den Webhook und aktualisieren Sie Ihren Endpunkt.
Kein X-Pricely-Signature-HeaderDer Webhook wurde ohne Secret gespeichert. Öffnen Sie den Dialog und klicken Sie auf „Webhook speichern“, um eines zu hinterlegen.
Dasselbe Ereignis kam zweimal anEine Wiederholung nach einer Zeitüberschreitung oder einem 5xx. Deduplizieren Sie anhand von X-Pricely-Delivery.
Kein invoice.overdue für eine alte RechnungDas ist so gewollt. Das Ereignis wird einmal gesendet, wenn die Rechnung ihr Fälligkeitsdatum erstmals überschreitet – nicht für Rechnungen, die schon vorher überfällig waren.

Tarife

Webhooks sind in Business und Enterprise enthalten. In anderen Tarifen zeigt die Karte „Webhooks“ ein Schloss-Symbol und einen Upgrade-Link.

War diese Seite hilfreich? Fehlt etwas oder ist etwas unklar? Schreiben Sie uns, dann verbessern wir die Seite.E-Mail an support@pricely.me
Esc
↑↓ zum Wechseln↵ zum ÖffnenEsc zum Schließen