Webhook Events
Webhooks ermöglichen es dir, Echtzeit-Benachrichtigungen über Ereignisse in deinem Account zu erhalten. Wenn ein Ereignis eintritt, sendet das System automatisch einen HTTP POST Request an die von dir konfigurierte URL.
Webhooks können über die API erstellt, aktualisiert und gelöscht werden. Die vollständige Dokumentation findest du in der Swagger-Dokumentation
# Signatur-Verifizierung
Alle Webhook-Anfragen enthalten eine kryptographische Signatur, mit der du überprüfen kannst, ob die Anfrage von Digiclose stammt und nicht manipuliert wurde. Wir verwenden HMAC-SHA256 zur Signierung des Payloads.
Signatur-Header
Jede Webhook-Anfrage enthält einen X-Webhook-Signature Header mit folgendem Format:
X-Webhook-Signature: t=1704067200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
| Komponente | Beschreibung |
|---|---|
t |
Unix-Timestamp zum Zeitpunkt der Signatur-Erstellung |
v1 |
HMAC-SHA256 Signatur (hex-kodiert) |
Verifizierungsschritte
Um eine Webhook-Signatur zu verifizieren, folge diesen Schritten:
- Extrahiere den Timestamp (t) und die Signatur (v1) aus dem X-Webhook-Signature Header.
- Erstelle den signierten Payload durch Verkettung von Timestamp, Punkt und dem rohen Request-Body: {timestamp}.{payload}
- Berechne den HMAC-SHA256 Hash des signierten Payloads mit deinem Webhook-Secret.
- Vergleiche deine berechnete Signatur mit der Signatur aus dem Header mittels timing-sicherem Vergleich.
$signatureHeader = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$payload = file_get_contents('php://input');
$secret = 'whsec_your_webhook_secret';
// Parse signature header
preg_match('/^t=(\d+),v1=([a-f0-9]+)$/', $signatureHeader, $matches);
$timestamp = $matches[1];
$signature = $matches[2];
// Verify timestamp (reject if older than 5 minutes)
if (abs(time() - $timestamp) > 300) {
http_response_code(400);
exit('Timestamp too old');
}
// Calculate expected signature
$signedPayload = $timestamp . '.' . $payload;
$expectedSignature = hash_hmac('sha256', $signedPayload, $secret);
// Compare signatures (timing-safe)
if (!hash_equals($expectedSignature, $signature)) {
http_response_code(400);
exit('Invalid signature');
}
// Signature valid - process webhook
$data = json_decode($payload, true);
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const signatureHeader = req.headers['x-webhook-signature'] || '';
const payload = JSON.stringify(req.body);
// Parse signature header
const match = signatureHeader.match(/^t=(\d+),v1=([a-f0-9]+)$/);
if (!match) {
throw new Error('Invalid signature format');
}
const timestamp = parseInt(match[1], 10);
const signature = match[2];
// Verify timestamp (reject if older than 5 minutes)
if (Math.abs(Date.now() / 1000 - timestamp) > 300) {
throw new Error('Timestamp too old');
}
// Calculate expected signature
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Compare signatures (timing-safe)
if (!crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signature)
)) {
throw new Error('Invalid signature');
}
return JSON.parse(payload);
}
// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
const data = verifyWebhookSignature(req, 'whsec_your_webhook_secret');
// Process webhook data
res.status(200).send('OK');
} catch (err) {
res.status(400).send(err.message);
}
});
Verifiziere immer die Signatur bevor du Webhooks verarbeitest. Lehne Anfragen mit Timestamps älter als 5 Minuten ab, um Replay-Attacken zu verhindern. Speichere dein Webhook-Secret sicher und exponiere es niemals in Client-seitigem Code.
# Payload-Struktur
Jeder Webhook-Request enthält einen JSON-Body mit folgender Grundstruktur:
{
"token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"event": "event_name",
"timestamp": 1768530865,
"data": [
"..."
]
}
| Feld | Typ | Beschreibung |
|---|---|---|
token |
String | Eindeutiger Identifikator des Webhooks (UUID) |
event |
String | Name des ausgelösten Events |
timestamp |
Integer | Unix-Timestamp des Ereignisses |
data |
OBJEKT | Event-spezifische Daten (siehe unten) |
# Verfügbare Events
Folgende Events können abonniert werden. Klicke auf ein Event, um die Payload-Struktur zu sehen.
# Kontakt
contact_created
Kontakt erstellt
Wird gefeuert, wenn ein neuer Kontakt erstellt wird.
Beispiel-Payload (data)
{
"contact": {
"id": 1234,
"created_at": "2024-01-25 10:30:00",
"full_name": "Max Mustermann",
"email": "[email protected]",
"phone": "+49151123456789",
"company": "Muster GmbH",
"formatted": "Max Mustermann (Muster GmbH) E-Mail: [email protected]",
"creator_id": 1,
"creator_name": "Admin User",
"operator_id": 2,
"operator_name": "Sales User",
"values": {
"firstname": "Max",
"lastname": "Mustermann",
"email": "[email protected]",
"phone": "+49151123456789",
"company": "Muster GmbH",
"street": "Musterstra\u00dfe",
"street_number": "42",
"postcode": "12345",
"city": "Berlin"
}
}
}
contact_updated
Kontakt aktualisiert
Wird gefeuert, wenn ein Kontakt aktualisiert wird.
Beispiel-Payload (data)
{
"contact": {
"id": 1234,
"created_at": "2024-01-25 10:30:00",
"full_name": "Max Mustermann",
"email": "[email protected]",
"phone": "+49151123456789",
"company": "Muster GmbH",
"formatted": "Max Mustermann (Muster GmbH) E-Mail: [email protected]",
"values": {
"firstname": "Max",
"lastname": "Mustermann",
"email": "[email protected]"
}
},
"changed_fields": [
{
"field": "phone",
"old_value": "+49151000000000",
"new_value": "+49151123456789"
}
]
}
contact_deleted
Kontakt gelöscht
Wird gefeuert, wenn ein Kontakt gelöscht wird.
Beispiel-Payload (data)
{
"contact": {
"id": 1234,
"full_name": "Max Mustermann",
"email": "[email protected]"
}
}
contact_field_changed
Benutzerfeld geändert
Wird gefeuert, wenn ein Benutzerfeld geändert wird.
Beispiel-Payload (data)
{
"type": "contact_field",
"field": {
"id": 1,
"name": "company",
"label": "Unternehmen",
"old_value": "Alte GmbH",
"new_value": "Neue GmbH"
},
"contact": {
"id": 1234,
"created_at": "2024-01-25 10:30:00",
"full_name": "Max Mustermann",
"email": "[email protected]",
"phone": "+49151123456789",
"company": "Neue GmbH",
"formatted": "Max Mustermann (Neue GmbH) E-Mail: [email protected]",
"creator_id": 1,
"creator_name": "Admin User",
"operator_id": 2,
"operator_name": "Sales User",
"values": {
"firstname": "Max",
"lastname": "Mustermann",
"email": "[email protected]",
"company": "Neue GmbH"
},
"deal_values": {
"custom_field": "custom_value"
}
}
}
contact_deal_phase_changed
Deal-Phase geändert
Wird gefeuert, wenn ein Kontakt zwischen Deal-Phasen verschoben wird.
Beispiel-Payload (data)
{
"pipeline": "Sales Pipeline",
"from": {
"id": 1,
"name": "Neuer Lead",
"color": "66,133,244"
},
"to": {
"id": 2,
"name": "Qualifiziert",
"color": "52,168,83"
},
"contact": {
"id": 1234,
"created_at": "2024-01-25 10:30:00",
"full_name": "Max Mustermann",
"email": "[email protected]",
"phone": "+49151123456789",
"company": "Muster GmbH",
"formatted": "Max Mustermann (Muster GmbH) E-Mail: [email protected]",
"values": {
"firstname": "Max",
"lastname": "Mustermann"
},
"deal_values": {
"deal_value": "5000"
}
}
}
# Deal
deal_created
Deal erstellt
Wird gefeuert, wenn ein neuer Deal erstellt wird.
Beispiel-Payload (data)
{
"deal": {
"id": 567,
"contact_id": 1234,
"pipeline_id": 1,
"pipeline_name": "Sales Pipeline",
"deal_phase_id": 1,
"deal_phase_name": "Neuer Lead",
"value": 5000,
"currency": "EUR",
"created_at": "2024-01-25 10:30:00"
},
"contact": {
"id": 1234,
"full_name": "Max Mustermann",
"email": "[email protected]"
}
}
deal_updated
Deal aktualisiert
Wird gefeuert, wenn ein Deal aktualisiert wird.
Beispiel-Payload (data)
{
"deal": {
"id": 567,
"contact_id": 1234,
"pipeline_id": 1,
"pipeline_name": "Sales Pipeline",
"deal_phase_id": 2,
"deal_phase_name": "Qualifiziert",
"value": 7500,
"currency": "EUR",
"updated_at": "2024-01-26 14:00:00"
},
"contact": {
"id": 1234,
"full_name": "Max Mustermann",
"email": "[email protected]"
}
}
# Pipeline
pipeline_created
Pipeline erstellt
Wird gefeuert, wenn eine neue Pipeline erstellt wird.
Beispiel-Payload (data)
{
"pipeline": {
"id": 1,
"name": "Sales Pipeline",
"created_at": "2024-01-25 10:30:00"
}
}
pipeline_updated
Pipeline aktualisiert
Wird gefeuert, wenn eine Pipeline aktualisiert wird.
Beispiel-Payload (data)
{
"pipeline": {
"id": 1,
"name": "Sales Pipeline (Aktualisiert)",
"updated_at": "2024-01-26 14:00:00"
}
}
# Aufgaben
task_created
Aufgabe erstellt
Wird gefeuert, wenn eine Aufgabe erstellt wird.
Beispiel-Payload (data)
{
"task": {
"id": 789,
"description": "R\u00fcckruf vereinbaren",
"category_id": 1,
"category_name": "Anruf",
"contact_id": 1234,
"contact_name": "Max Mustermann",
"assignee_id": 2,
"assignee_name": "Sales User",
"due_at": "2024-01-27 10:00:00",
"status": "open",
"created_at": "2024-01-25 10:30:00"
}
}
task_completed
Aufgabe abgeschlossen
Wird gefeuert, wenn eine Aufgabe abgeschlossen wurde.
Beispiel-Payload (data)
{
"task": {
"id": 789,
"description": "R\u00fcckruf vereinbaren",
"category_id": 1,
"category_name": "Anruf",
"contact_id": 1234,
"contact_name": "Max Mustermann",
"assignee_id": 2,
"assignee_name": "Sales User",
"due_at": "2024-01-27 10:00:00",
"status": "completed",
"completed_at": "2024-01-26 15:30:00"
}
}
# Tags
tag_created
Tag erstellt
Wird gefeuert, wenn ein neuer Tag erstellt wird.
Beispiel-Payload (data)
{
"tag": {
"id": 123,
"name": "VIP-Kunde",
"color": "#FF5733",
"created_at": "2024-01-25 10:30:00"
}
}
tag_added_to_contact
Tag zu Kontakt hinzugefügt
Wird gefeuert, wenn ein Tag zu einem Kontakt hinzugefügt wird.
Beispiel-Payload (data)
{
"tag": {
"id": 123,
"name": "VIP-Kunde",
"color": "#FF5733"
},
"contact": {
"contact_id": 1234,
"firstname": "Max",
"lastname": "Mustermann",
"email": "[email protected]",
"phone": "+49151123456789",
"company": "Muster GmbH"
}
}
tag_removed_from_contact
Tag von Kontakt entfernt
Wird gefeuert, wenn ein Tag von einem Kontakt entfernt wird.
Beispiel-Payload (data)
{
"tag": {
"id": 123,
"name": "VIP-Kunde",
"color": "#FF5733"
},
"contact": {
"contact_id": 1234,
"firstname": "Max",
"lastname": "Mustermann",
"email": "[email protected]",
"phone": "+49151123456789",
"company": "Muster GmbH"
}
}
# Angebote
offer_seen
Angebot gesehen
Wird gefeuert, wenn ein Kontakt ein Angebot gesehen hat.
Beispiel-Payload (data)
{
"offer": {
"id": 456,
"number": "ANG-2024-0001",
"contact_id": 1234,
"contact_name": "Max Mustermann",
"total_net": 1000,
"total_gross": 1190,
"currency": "EUR",
"status": "sent",
"seen_at": "2024-01-26 09:15:00",
"created_at": "2024-01-25 10:30:00",
"items": [
{
"product_id": 1,
"product_name": "Beratungspaket",
"quantity": 1,
"unit_price": 1000,
"total": 1000
}
]
}
}
offer_accepted
Angebot akzeptiert
Wird gefeuert, wenn ein Kontakt ein Angebot angenommen hat.
Beispiel-Payload (data)
{
"offer": {
"id": 456,
"number": "ANG-2024-0001",
"contact_id": 1234,
"contact_name": "Max Mustermann",
"total_net": 1000,
"total_gross": 1190,
"currency": "EUR",
"status": "accepted",
"accepted_at": "2024-01-26 14:30:00",
"created_at": "2024-01-25 10:30:00",
"items": [
{
"product_id": 1,
"product_name": "Beratungspaket",
"quantity": 1,
"unit_price": 1000,
"total": 1000
}
]
}
}