Webhooks

Erhalte Echtzeit-Benachrichtigungen über Spenden und Bestellungen in deinem System durch automatische HTTP-POST-Anfragen an deine konfigurierten Endpunkte.

Webhooks informieren dein System in Echtzeit über Spenden, Newsletter-Anmeldungen und Shop-Bestellungen. Sobald ein Ereignis eintritt, sendet Soulclick eine HTTP-POST-Anfrage mit den Daten an deinen Endpunkt.

Payload-Versionen

Jeder Endpunkt hat eine Payload-Version. Sie bestimmt, welche Felder er erhält. Eine Version liefert weiterhin genau die Felder, mit denen du integriert hast: Neue Felder kommen erst, wenn du im Admin unter «Webhook Endpunkte» auf eine neuere Version wechselst.

VersionEnthält
v1Die bisherige Payload, created_at in data in Schweizer Zeit mit Offset. Voreinstellung für neue Endpunkte.
v2Zusätzlich donor.custom_fields: die Antworten auf die eigenen Felder des Spendenformulars, siehe Donor-Felder. Alle Zeitangaben in UTC.
v2.1Zusätzlich positions in donation.completed: Ein Multi-Checkout kommt als ein einziges Event, siehe Multi-Checkout. amount und die Beträge der Positionen stehen in der Währung der Spende. Wählbar, sobald «Spendenshop Warenkorb Logik aktivieren» im Abschnitt «Warenkorb» unter Eigenschaften eingeschaltet ist.

Für neue Integrationen empfehlen wir die neueste Version, die der Admin anbietet.

Einrichtung

  1. Öffne im Admin «Webhook Endpunkte». Pro Plattform gibt es einen Endpunkt.
  2. URL: Gib die vollständige HTTPS-URL deines Endpunkts ein.
  3. Aktiv: Setze den Endpunkt auf aktiv.
  4. Payload-Version: Wähle die Version, siehe Payload-Versionen.
  5. Ereignisse: Wähle, welche Events der Endpunkt erhält. donation.completed, donation.recurring_completed und shop_order.completed sind voreingestellt. donation.reconciled und newsletter.subscribed sind aus, auch bei bestehenden Endpunkten, bis du sie hier aktivierst.
  6. Basic Auth (optional): Hinterlege Benutzername und Passwort, die Soulclick bei jeder Anfrage mitsendet.

Events

Jedes Event hat denselben Rahmen: id, event, created_at, environment und die eigentlichen Daten unter data, siehe Envelope.

Soulclick kann jederzeit neue Events einführen. Verarbeite nur die event-Werte, die du kennst, und beantworte alle anderen trotzdem mit 2xx, sonst wiederholt Soulclick die Zustellung.

Ein Endpunkt erhält nur die Events, die bei ihm unter «Ereignisse» ausgewählt sind, siehe Einrichtung.

donation.completed

Wird ausgelöst, wenn eine Spende erfolgreich abgeschlossen wurde. Enthält die Spende, die spendende Person, die Zahlung und bei QR-Rechnungen die Referenz.

Beispiel Payload (v1, Einzelspende):

{
  "id": 123,
  "event": "donation.completed",
  "created_at": "2024-01-15T10:30:00Z",
  "environment": "prod",
  "data": {
    "donation": {
      "id": "150120240001",
      "created_at": "2024-01-15T11:30:00+01:00",
      "amount": 50.0,
      "currency": "CHF",
      "campaign": {
        "id": 123,
        "title": "Hilfsprojekt Afrika"
      },
      "purpose": "Unterstützung für Bildungsprojekte",
      "donor": {
        "gender": "male",
        "first_name": "Max",
        "last_name": "Mustermann",
        "email": "[email protected]",
        "company": "Beispiel AG",
        "po_box": null,
        "street": "Musterstrasse",
        "street_number": "123",
        "postal_code": "8000",
        "city": "Zürich",
        "country": "CH",
        "language": "de",
        "newsletter": true
      },
      "payment": {
        "transaction_id": "TXN-ABC123",
        "method": "VIS",
        "status": "completed"
      },
      "invoice": null
    }
  }
}

In v2 kommt donor.custom_fields dazu, in v2.1 zusätzlich positions.

Multi-Checkout

Legt eine Person mehrere Spenden in den Warenkorb, bezahlt sie diese mit einer einzigen Zahlung. Ab v2.1 sendet Soulclick dafür ein einziges donation.completed: id ist die Nummer des Multi-Checkouts, wie sie im Soulclick Manager steht, amount der Gesamtbetrag aller Positionen, und donor, payment und invoice gelten für die ganze Zahlung. campaign und purpose der Spende sind null, weil jede Position ihre eigenen trägt:

"donation": {
  "id": "220920260006",
  "amount": 41.45,
  "currency": "CHF",
  "campaign": null,
  "purpose": null,
  "positions": [
    {"id": "220920260006", "amount": 10.35, "campaign": {"id": 68, "title": "Baumpatenschaft"}, "purpose": null},
    {"id": "220920260007", "amount": 10.35, "campaign": {"id": 16, "title": "Nothilfe"}, "purpose": null},
    {"id": "220920260008", "amount": 20.75, "campaign": {"id": 4, "title": "Wasserprojekt"}, "purpose": null}
  ]
}

Eine Einzelspende führt sich selbst als einzige Position, campaign und purpose stehen dann wie gewohnt auch an der Spende. Die Felder einer Position stehen unter Positions-Felder.

Endpunkte auf v1 oder v2 erhalten wie bisher ein donation.completed pro Position, jedes mit Nummer, Betrag und Kampagne dieser Position.

Denselben Aufbau liefert die API ab /api/v2.1/ bei den Spenden eines Kontakts, siehe API-Referenz.

donation.recurring_completed

Wird bei jeder Folgezahlung einer Dauerspende ausgelöst. Die Payload hat denselben Aufbau wie donation.completed, ohne positions: Dauerspenden können nicht in den Warenkorb gelegt werden.

Online-Zahlung (Datatrans, Payrexx, Stripe)

Wird ausgelöst, sobald eine Folgezahlung per Karte, TWINT oder einer anderen Online-Zahlungsmethode erfolgreich war.

Beispiel Payload:

{
  "id": 789,
  "event": "donation.recurring_completed",
  "created_at": "2024-02-15T08:00:00Z",
  "environment": "prod",
  "data": {
    "donation": {
      "id": "150220240001",
      "created_at": "2024-02-15T08:00:00Z",
      "amount": 50.0,
      "currency": "CHF",
      "campaign": {
        "id": 123,
        "title": "Hilfsprojekt Afrika"
      },
      "purpose": "Unterstützung für Bildungsprojekte",
      "donor": {
        "gender": "male",
        "first_name": "Max",
        "last_name": "Mustermann",
        "email": "[email protected]",
        "company": null,
        "po_box": null,
        "street": "Musterstrasse",
        "street_number": "123",
        "postal_code": "8000",
        "city": "Zürich",
        "country": "CH",
        "language": "de",
        "newsletter": true
      },
      "payment": {
        "transaction_id": "240215100000",
        "method": "VIS",
        "status": "completed"
      },
      "invoice": null
    }
  }
}

Rechnungszahlung (QR-Rechnung)

Wird ausgelöst, wenn eine wiederkehrende QR-Rechnung erstellt und versendet wurde.

Beispiel Payload:

{
  "id": 790,
  "event": "donation.recurring_completed",
  "created_at": "2024-02-15T08:00:00Z",
  "environment": "prod",
  "data": {
    "donation": {
      "id": "150220240002",
      "created_at": "2024-02-15T08:00:00Z",
      "amount": 50.0,
      "currency": "CHF",
      "campaign": {
        "id": 123,
        "title": "Hilfsprojekt Afrika"
      },
      "purpose": "Unterstützung für Bildungsprojekte",
      "donor": {
        "gender": "male",
        "first_name": "Max",
        "last_name": "Mustermann",
        "email": "[email protected]",
        "company": null,
        "po_box": null,
        "street": "Musterstrasse",
        "street_number": "123",
        "postal_code": "8000",
        "city": "Zürich",
        "country": "CH",
        "language": "de",
        "newsletter": true
      },
      "payment": {
        "transaction_id": "000000000150220240000200001",
        "method": "INV",
        "status": "pending"
      },
      "invoice": "000000000150220240000200001"
    }
  }
}

Unterschiede zu donation.completed:

Felddonation.completeddonation.recurring_completed (Online)donation.recurring_completed (Rechnung)
payment.methodZahlungsmethode, z.B. "VIS" oder "TWI"Zahlungsmethode der Erstspende"INV"
payment.status"completed" oder "pending""completed""pending"
AuslöserErstmalige SpendeFolgezahlung über Datatrans, Payrexx oder StripeWiederkehrende QR-Rechnung

donation.reconciled

Standardmässig aus: Aktiviere «Spendengebühren abgeglichen» beim Endpunkt unter «Ereignisse».

Wird ausgelöst, sobald der Payment-Provider Gebühr und Auszahlungsbetrag einer Zahlung geliefert hat, siehe Reconciliation. Das gilt für Einzelspenden und Folgezahlungen von Dauerspenden über Stripe und Payrexx und ist Teil des Campaigner-Pakets. Jede Zahlung wird genau einmal gemeldet, je nach Payment-Provider direkt oder einige Stunden nach donation.completed bzw. donation.recurring_completed.

Die Payload ist dieselbe wie im Event, das die Zahlung gemeldet hat, payment enthält zusätzlich paid_out_amount, fee_amount und reconciled, siehe Payment-Felder. Die beiden Beträge stehen in der Auszahlungswährung des Payment-Providers, die von currency abweichen kann, zum Beispiel bei einer Spende in EUR, die in CHF ausbezahlt wird.

Wird das Event, das die Zahlung gemeldet hat, gerade wiederholt, kann donation.reconciled vorher ankommen. Lege die Spende darum über ihre id an oder aktualisiere sie, egal welches der beiden Events zuerst eintrifft.

Beispiel Payload (v1, Einzelspende):

{
  "id": 124,
  "event": "donation.reconciled",
  "created_at": "2024-01-17T06:00:00Z",
  "environment": "prod",
  "data": {
    "donation": {
      "id": "150120240001",
      "created_at": "2024-01-15T11:30:00+01:00",
      "amount": 50.0,
      "currency": "CHF",
      "campaign": {
        "id": 123,
        "title": "Hilfsprojekt Afrika"
      },
      "purpose": "Unterstützung für Bildungsprojekte",
      "donor": {
        "gender": "male",
        "first_name": "Max",
        "last_name": "Mustermann",
        "email": "[email protected]",
        "company": null,
        "po_box": null,
        "street": "Musterstrasse",
        "street_number": "123",
        "postal_code": "8000",
        "city": "Zürich",
        "country": "CH",
        "language": "de",
        "newsletter": true
      },
      "payment": {
        "transaction_id": "TXN-ABC123",
        "method": "VIS",
        "status": "completed",
        "paid_out_amount": 48.55,
        "fee_amount": 1.45,
        "reconciled": true
      },
      "invoice": null
    }
  }
}

Bei einem Multi-Checkout gilt dieselbe Aufteilung wie bei donation.completed: Ab v2.1 kommt ein einziges Event mit positions, paid_out_amount und fee_amount sind die Summen aller Positionen. Endpunkte auf v1 oder v2 erhalten ein Event pro Position mit ihrem Anteil an Gebühr und Auszahlungsbetrag.

newsletter.subscribed

Standardmässig aus: Aktiviere «Newsletter-Anmeldung bestätigt» beim Endpunkt unter «Ereignisse».

Wird ausgelöst, sobald eine Person ihre Newsletter-Anmeldung über den Link in der Bestätigungsmail bestätigt (Double-Opt-in), egal ob sie sich im Spendenformular, über ein Formular oder über die API angemeldet hat. Ein weiterer Klick auf den Link löst kein neues Event aus.

Beispiel Payload (v2, v2.1):

{
  "id": 812,
  "event": "newsletter.subscribed",
  "created_at": "2024-01-15T10:32:00Z",
  "environment": "prod",
  "data": {
    "newsletter_subscription": {
      "subscription_id": 2187,
      "contact_id": 4711,
      "gender": "male",
      "first_name": "Max",
      "last_name": "Mustermann",
      "email": "[email protected]",
      "language": "de",
      "opt_in": true,
      "opt_in_date": "2024-01-15T10:32:00Z"
    }
  }
}
FeldTypBeschreibung
subscription_idintegerNummer der Anmeldung
contact_idintegerVerknüpfter Kontakt, oder null
genderstring"male", "female", "prefer-not-to-say" oder "", wenn unbekannt
first_namestringVorname, oder null
last_namestringNachname, oder null
emailstringE-Mail-Adresse
languagestringSprachcode (ISO 639-1)
opt_inbooleanImmer true
opt_in_datestringZeitpunkt der Bestätigung in UTC. Endpunkte auf v1 erhalten ihn in Schweizer Zeit mit Offset, z.B. "2024-01-15T11:32:00+01:00"

Ist die Anmeldung mit einem Kontakt verknüpft, zum Beispiel bei einer Anmeldung im Spendenformular, stammen Anrede, Name, E-Mail und Sprache von diesem Kontakt. Was dem Kontakt fehlt, kommt aus der Anmeldung. newsletter_subscription ist derselbe Eintrag, den GET /api/v2.1/crm/newsletter-subscriptions/ liefert.

shop_order.completed

Wird ausgelöst, wenn eine Shop-Bestellung erfolgreich abgeschlossen wurde, für registrierte Kund:innen wie für Gastbestellungen.

Beispiel Payload:

{
  "id": 456,
  "event": "shop_order.completed",
  "created_at": "2024-01-15T11:00:00Z",
  "environment": "prod",
  "data": {
    "shop_order": {
      "id": "ORDER-2024-001",
      "created_at": "2024-01-15T11:00:00Z",
      "customer": {
        "gender": "female",
        "first_name": "Anna",
        "last_name": "Beispiel",
        "email": "[email protected]",
        "phone": "+41 44 123 45 67",
        "company": "Firma GmbH",
        "department": "Marketing",
        "care_of": null,
        "po_box": null,
        "street": "Beispielweg",
        "street_number": "456",
        "postal_code": "3000",
        "city": "Bern",
        "country": "CH",
        "language": "de"
      },
      "invoice_address": {
        "first_name": "Anna",
        "last_name": "Beispiel",
        "company": "Firma GmbH",
        "department": "Marketing",
        "care_of": null,
        "po_box": null,
        "street": "Beispielweg",
        "street_number": "456",
        "postal_code": "3000",
        "city": "Bern",
        "country": "CH"
      },
      "delivery_address": {
        "first_name": "Anna",
        "last_name": "Beispiel",
        "company": "Firma GmbH",
        "department": "Marketing",
        "care_of": null,
        "po_box": null,
        "street": "Beispielweg",
        "street_number": "456",
        "postal_code": "3000",
        "city": "Bern",
        "country": "CH"
      },
      "items": [
        {
          "article_number": "ART-001",
          "quantity": 2,
          "unit_price_net": 25.0,
          "unit_price_gross": 27.0,
          "discount_percentage": 0,
          "total_price_gross": 54.0
        }
      ],
      "currency": "CHF",
      "total_price_gross": 59.0,
      "total_vat": 4.0,
      "total_discount": 0.0,
      "payment": {
        "transaction_id": "TXN-DEF456",
        "method": "PAYPAL",
        "status": "completed"
      },
      "shipping": {
        "method": "standard",
        "cost": 5.0
      }
    }
  }
}

Zustellung

Soulclick stellt Events automatisch zu. Eine Antwort mit einem 2xx-Statuscode gilt als zugestellt. Jeder andere Statuscode, ein Verbindungsfehler oder eine Antwort nach mehr als 30 Sekunden gilt als fehlgeschlagen.

Wiederholungen

Ein fehlgeschlagenes Event wird mit wachsendem Abstand erneut gesendet, mit derselben id:

Fehlgeschlagener VersuchNächster Versuch nach
15 Minuten
215 Minuten
31 Stunde
44 Stunden

Schlägt auch der fünfte Versuch fehl, erhält das Event den Status «Maximale Wiederholungen erreicht» und wird nicht mehr gesendet.

Event-Status

Im Admin unter «Webhook Events» siehst du jedes Event mit seinem Status:

StatusBedeutung
WartendWartet auf die Zustellung
ZugestelltDein Endpunkt hat mit 2xx geantwortet
FehlgeschlagenZustellung fehlgeschlagen, wird wiederholt
Maximale Wiederholungen erreichtFünf Versuche fehlgeschlagen, keine weitere Zustellung

Troubleshooting

Events werden nicht gesendet:

  • Prüfe im Admin unter «Webhook Endpunkte», ob ein Endpunkt eingerichtet ist.
  • Stelle sicher, dass der Webhook-Endpunkt aktiv ist.
  • Prüfe, ob das Event beim Endpunkt unter «Ereignisse» ausgewählt ist.

Events schlagen fehl:

  • Prüfe «Letzter Response Code» und «Letzter Response Body» des Events im Admin.
  • Stelle sicher, dass dein Endpunkt aus dem Internet erreichbar ist und mit 2xx antwortet.
  • Prüfe Firewall und Netzwerkeinstellungen.

Bei anhaltenden Problemen hilft dir der Soulclick-Support.

Endpunkt implementieren

Beispiel-Handler (Python/Flask)

from flask import Flask, jsonify, request

app = Flask(__name__)


@app.route('/webhook', methods=['POST'])
def handle_webhook():
    event = request.get_json()
    event_type = event['event']
    data = event['data']

    if event_type == 'donation.completed':
        donation = data['donation']
        # Ab v2.1 listet positions jede Position, davor ist die Spende selbst die Position
        for position in donation.get('positions', [donation]):
            print(f"Position {position['id']}: {position['amount']} {donation['currency']}")

    elif event_type == 'donation.recurring_completed':
        donation = data['donation']
        print(f"Folgezahlung: {donation['amount']} {donation['currency']}, {donation['payment']['status']}")

    elif event_type == 'donation.reconciled':
        payment = data['donation']['payment']
        print(f"Gebühren abgeglichen: {payment['paid_out_amount']} ausbezahlt, {payment['fee_amount']} Gebühr")

    elif event_type == 'newsletter.subscribed':
        subscription = data['newsletter_subscription']
        print(f"Newsletter: {subscription['email']} seit {subscription['opt_in_date']}")

    elif event_type == 'shop_order.completed':
        order = data['shop_order']
        print(f"Bestellung {order['id']}: {order['total_price_gross']} {order['currency']}")

    # Alle anderen Events bestätigen und ignorieren
    return jsonify({'status': 'success'}), 200

Best Practices

  1. Idempotenz: Ein Event kann mehrfach ankommen. Erkenne Wiederholungen an der id des Events.
  2. Schnell antworten: Bestätige mit 2xx innerhalb von 30 Sekunden und verarbeite aufwendige Logik danach asynchron, zum Beispiel über eine Queue.
  3. Unbekanntes ignorieren: Beantworte ein event, das du nicht verarbeitest, mit 2xx und ignoriere es. Verlasse dich nicht auf die Reihenfolge der Felder und lehne keine Payload wegen eines zusätzlichen Feldes ab.
  4. Absichern: Verwende HTTPS und Basic Auth.
  5. Protokollieren: Logge eingehende Events, damit du Fehler nachvollziehen kannst.

Datenfelder-Referenz

Envelope

FeldTypBeschreibung
idintegerEindeutige Nummer des Events, bei Wiederholungen gleich
eventstringdonation.completed, donation.recurring_completed, donation.reconciled, newsletter.subscribed oder shop_order.completed. Weitere können dazukommen.
created_atstringZeitpunkt des Events
environmentstring"prod", "stage" oder "test"
dataobjectDie Daten des Events unter donation, newsletter_subscription oder shop_order

Donor-Felder

FeldTypBeschreibung
genderstring"male", "female" oder "prefer-not-to-say"
first_namestringVorname
last_namestringNachname
emailstringE-Mail-Adresse
companystringFirmenname (optional)
po_boxstringPostfach (optional)
streetstringStrasse
street_numberstringHausnummer
postal_codestringPostleitzahl
citystringOrt
countrystringLändercode (ISO 3166-1 alpha-2)
languagestringSprachcode (ISO 639-1)
newsletterbooleanEinwilligung in den Newsletter
custom_fieldsobjectAntworten auf die eigenen Felder des Spendenformulars (ab v2)

Beispiel custom_fields:

"custom_fields": {
  "trauerfall": "Anna Muster",
  "newsletter_print": true
}

Die Schlüssel sind die im Admin vergebenen Feldnamen, die Werte Text oder bei Checkboxen true und false. Hat eine Spende keine eigenen Felder, fehlt custom_fields ganz.

Positions-Felder

positions gibt es ab v2.1 in donation.completed, siehe Multi-Checkout.

FeldTypBeschreibung
idstringNummer der Position. Die erste trägt die Nummer des Multi-Checkouts.
amountnumberBetrag dieser Position
campaignobjectKampagne dieser Position mit id und title, oder null
purposestringSpendenzweck dieser Position, oder null

Payment-Felder

FeldTypBeschreibung
transaction_idstringEindeutige Transaktions-ID der Zahlung
methodstringCode der Zahlungsmethode, siehe Zahlungsmethoden
statusstring"completed" oder "pending"
paid_out_amountnumberNur in donation.reconciled: Betrag, den der Payment-Provider nach Abzug seiner Gebühr auszahlt, in seiner Auszahlungswährung
fee_amountnumberNur in donation.reconciled: Gebühr des Payment-Providers, in seiner Auszahlungswährung
reconciledbooleanNur in donation.reconciled: true, sobald Gebühr und Auszahlungsbetrag aller Positionen bekannt sind

Invoice-Feld

invoice ist in allen Spenden-Events vorhanden und enthält die Referenz, die auf der QR-Rechnung steht. Bei Rechnungszahlungen ("INV") ist das je nach Konfiguration:

  • QR-Referenznummer aktiv (Standard): die 27-stellige QR-Referenznummer, z.B. "000000000150220240000200001"
  • Benutzerdefinierter Text: der konfigurierte Text, z.B. "Spende für Kinderhilfe"
  • Spendenzweck: der Spendenzweck des Produkts, z.B. "Zweck: Bildung"

Bei allen anderen Zahlungsmethoden ist invoice null.

Datenformate

  • Zeitangaben: ISO 8601 in UTC, z.B. "2024-01-15T10:30:00Z". Endpunkte auf v1 erhalten created_at und opt_in_date in data in Schweizer Zeit mit Offset, z.B. "2024-01-15T11:30:00+01:00"
  • Beträge: was bezahlt wurde, inklusive der Gebühren, die die spendende Person übernommen hat, als Zahl mit bis zu 2 Nachkommastellen, z.B. 50 oder 50.5 für CHF 50.00 bzw. CHF 50.50
  • Währungen: ISO 4217, z.B. "CHF", "EUR"
  • Länder: ISO 3166-1 alpha-2, z.B. "CH", "DE"
  • Sprachen: ISO 639-1, z.B. "de", "fr"

Zahlungsmethoden

Die häufigsten Codes in payment.method:

CodeZahlungsmethode
ECAMastercard
VISVisa
TWITWINT
PFCPostFinance Card
PEFPostFinance E-Finance
PAPPayPal
INVQR-Rechnung

Die vollständigen Listen stehen in der Datatrans-Dokumentation und der Payrexx-Dokumentation.


Did this page help you?