Zum Hauptinhalt springen

Webhooks - Dokumentation

Tomorro-Aktionen, die die Automatisierung auslösen können

  1. Ein Vertrag wurde erstellt

  2. Ein Vertrag wurde gelöscht

  3. Ein Vertrag wurde vollständig unterzeichnet

  4. Ein Vertragsstatus wurde geändert

  5. Eine automatische Erinnerung wurde ausgelöst

Einen Webhook in Tomorro erstellen

Gehen Sie zu “Webhook hinzufügen” :

4 Schritte sind hier erforderlich:

  1. Ihren Webhook benennen

  2. Die zuvor erstellte URL einfügen, um das Ereignis zu empfangen

  3. Auswählen, welcher Auslöser die Automatisierung aktiviert (kann mehrere sein)

  4. Den Webhook erstellen

Hinweis: Webhooks sind personenbezogen.

Bsp.: Der Webhook zur Vertragserstellung wird nur ausgelöst, wenn ich Teilnehmer bin. Der Webhook zur automatischen Erinnerung wird nur ausgelöst, wenn die Erinnerung an mich gerichtet ist.

Webhook Beispiel

Hier ist ein Beispiel eines vollständigen Webhooks (zur Vertragserstellung), um die Daten weiterverarbeiten zu können:

{
"eventId": "b21213e3-8a9a-4e04-9bfc-c4e53f123xxx",
"webhookId": "2a76094c-1f2e-48c8-a47f-1add41234xxx",
"createdAt": "2025-03-25T14:55:16.280Z",
"eventType": "contractCreated",
"data": {
"contract": {
"id": "6954ad41-19f3-446f-b0db-fcc661234xxx",
"name": "TestWebhook",
"organizationId": "115e926b-9c61-4172-8609-02212344exxx",
"author": {
"id": "974d2a1c-eb63-4237-877e-37d12341axxx",
"user": {
"id": "fd61234d-a4e1-4103-8d31-59e351d2bxxx",
"username": "paul.lubet@tomorro.com",
"firstname": "Paul",
"lastname": "Lubet"
}
},
"referent": {
"id": "974d2a1c-eb63-4237-877e-37123451axxx",
"user": {
"id": "fd61234d-a4e1-4103-8d31-59e351d2bxxx",
"username": "paul.lubet@tomorro.com",
"firstname": "Paul",
"lastname": "Lubet"
}
},
"referentId": "974d2a1c-eb63-4237-877e-37123451axxx",
"supervisorId": null,
"externalCompany": {
"id": "01611234-7cdf-4785-966e-ee06b74ccxxx",
"name": "My external company"
},
"priorNoticeDuration": "undefined",
"priorNoticeValue": null,
"renewal": "no",
"signatureDate": null,
"status": "draft",
"createdAt": "2025-03-25T14:55:14.690Z",
"updatedAt": "2025-03-25T14:55:14.000Z",
"documentId": null,
"typeId": null,
"templateId": null,
"contractAttributes": [
{
"attributeDefinition": {
"name": "durationType"
},
"value": "PERMANENT"
},
{
"attributeDefinition": {
"name": "endAt"
},
"value": null
},
{
"attributeDefinition": {
"name": "externalPartyName"
},
"value": "My external company"
},
{
"attributeDefinition": {
"name": "initialDuration"
},
"value": null
},
{
"attributeDefinition": {
"name": "internalPartyName"
},
"value": "Paul Lubet"
},
{
"attributeDefinition": {
"name": "language"
},
"value": "fr"
},
{
"attributeDefinition": {
"name": "nextRenewalDate"
},
"value": null
},
{
"attributeDefinition": {
"name": "priorNotice"
},
"value": null
},
{
"attributeDefinition": {
"name": "renewalDuration"
},
"value": null
},
{
"attributeDefinition": {
"name": "renewalType"
},
"value": "no"
},
{
"attributeDefinition": {
"name": "signatureDate"
},
"value": null
},
{
"attributeDefinition": {
"name": "startAt"
},
"value": null
}
],
"attributes": {
"durationType": {
"value": "PERMANENT",
"name": "durationType",
"attributeDefinitionId": "durationType"
},
"endAt": {
"value": null,
"name": "endAt",
"attributeDefinitionId": "endAt"
},
"externalPartyName": {
"value": "My external company",
"name": "externalPartyName",
"attributeDefinitionId": "externalPartyName"
},
"initialDuration": {
"value": null,
"name": "initialDuration",
"attributeDefinitionId": "initialDuration"
},
"internalPartyName": {
"value": "Paul Lubet",
"name": "internalPartyName",
"attributeDefinitionId": "internalPartyName"
},
"language": {
"value": "fr",
"name": "language",
"attributeDefinitionId": "language"
},
"nextRenewalDate": {
"value": null,
"name": "nextRenewalDate",
"attributeDefinitionId": "nextRenewalDate"
},
"priorNotice": {
"value": null,
"name": "priorNotice",
"attributeDefinitionId": "priorNotice"
},
"renewalDuration": {
"value": null,
"name": "renewalDuration",
"attributeDefinitionId": "renewalDuration"
},
"renewalType": {
"value": "no",
"name": "renewalType",
"attributeDefinitionId": "renewalType"
},
"signatureDate": {
"value": null,
"name": "signatureDate",
"attributeDefinitionId": "signatureDate"
},
"startAt": {
"value": null,
"name": "startAt",
"attributeDefinitionId": "startAt"
}
}
}
}
}

Technische Dokumentation

🔐 Sicherheitsrichtlinie

Header-Signatur

Wir signieren Webhook-Ereignisse, indem wir eine Signatur im Leeway_Signature-Header jedes Ereignisses einfügen.

Jeder Webhook verfügt über ein automatisch generiertes Secret, das zwischen uns und dem Endnutzer geteilt wird. Zur Signierung des Ereignisses berechnen wir einen HMAC-SHA256 aus dem Zeitstempel und dem Ereignis-Body:

sha256 = HMAC_SHA256(timestamp + "." + eventBody)

Der Leeway_Signature-Header hat folgendes Format:

Leeway-Signature: t=1492774577, sha256=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

wobei t den Zeitstempel und sha256 den zuvor berechneten Hash darstellt.

Hinweis: Zeilenumbrüche wurden zur besseren Lesbarkeit hinzugefügt, ein echter Leeway_Signature-Header steht jedoch in einer einzigen Zeile.

Signaturprüfung

Wenn ein Client ein Webhook-Ereignis empfängt, sollte er die Gültigkeit der Signatur prüfen. Hier ein Beispiel:

import {createHmac, timingSafeEqual} from 'crypto';    function checkSignature(signature, secret, body) {       const timestamp = signature[0].split('=')[1];       const sig = Buffer.from(signature[1].split('=')[1], 'utf8');        const hmac = createHmac('sha256', secret);       const digest = Buffer.from(hmac.update(timestamp + '.' + JSON.stringify(body)).digest('hex'), 'utf8');       if (sig.length !== digest.length || !timingSafeEqual(digest, sig)) {      return false;       } else {           return true;       }  }

Es wird empfohlen, einen zeitkonstanten Zeichenfolgenvergleich zu verwenden, um Timing-Angriffe zu vermeiden. Deshalb wird hier timingSafeEqual anstelle von === verwendet (siehe https://en.wikipedia.org/wiki/Timing_attack).

Darüber hinaus sollte der Client prüfen, ob die Differenz zwischen dem empfangenen Zeitstempel und dem aktuellen Zeitstempel innerhalb seiner Toleranz liegt (zum Schutz vor Replay-Angriffen).


🔂 Wiederholungsrichtlinie

Wenn der Webhook mit einem anderen Code als 2xx antwortet oder zu lange braucht (das Timeout ist derzeit auf 3 Sekunden gesetzt, kann sich jedoch ändern), wird der Versuch als fehlgeschlagen gewertet.

In diesem Fall wird, sofern die Anzahl der Versuche für dieses WebhookEvent unter dem zulässigen Maximum liegt (derzeit MAX_RETRIES = 3), ein neues Ereignis mit einer Verzögerung von 5 Minuten in die Warteschlange gestellt (auch dieser Wert kann sich ändern).

Andernfalls wird kein weiterer Versuch unternommen und der Webhook deaktiviert.

Hat dies deine Frage beantwortet?