Ein Vertrag wurde erstellt
Ein Vertrag wurde gelöscht
Ein Vertrag wurde vollständig unterzeichnet
Ein Vertragsstatus wurde geändert
Eine automatische Erinnerung wurde ausgelöst
Einen Webhook in Tomorro erstellen
Einen Webhook in Tomorro erstellen
Gehen Sie zu Tomorro > Mein Konto > Integrationen > Webhooks:
Gehen Sie zu “Webhook hinzufügen” :
4 Schritte sind hier erforderlich:
Ihren Webhook benennen
Die zuvor erstellte URL einfügen, um das Ereignis zu empfangen
Auswählen, welcher Auslöser die Automatisierung aktiviert (kann mehrere sein)
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
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
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.



