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.

Webhook-Verwaltung per API
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:

Header-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:

  1. Extrahiere den Timestamp (t) und die Signatur (v1) aus dem X-Webhook-Signature Header.
  2. Erstelle den signierten Payload durch Verkettung von Timestamp, Punkt und dem rohen Request-Body: {timestamp}.{payload}
  3. Berechne den HMAC-SHA256 Hash des signierten Payloads mit deinem Webhook-Secret.
  4. 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);
    }
});
Sicherheitsempfehlungen
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:

Basis-Payload
{
    "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
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
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
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
Kontakt
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
Kontakt
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
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
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
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
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
Aufgaben
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
Aufgaben
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
Tags
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
Tags
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
Tags
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
Angebote
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
Angebote
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
            }
        ]
    }
}