Ein Webhook ist ein POST, das GREM an Ihre URL sendet, wenn etwas passiert: Ein Auftrag endet, ein Objekt oder ein Lead ändert sich. Die Verwaltung von Abonnements braucht den Bereich webhooks und ist kostenlos. Webhooks einer Agentur werden mit dem agenturweiten Schlüssel verwaltet; ein Agentenschlüssel erhält 403.
Abonnieren
curl -X POST https://developers.grem.capital/api/v1/webhooks \
-H "Authorization: Bearer gsk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/grem-hook","events":["content.completed","content.failed","lead.created"]}'
{ "id": "...", "url": "https://example.com/grem-hook", "events": [...], "status": "active",
"failureCount": 0, "lastDeliveryAt": null, "secret": "whsec_..." }
url- eine öffentlichehttps://-Adresse auf dem Standardport (443), ohneuser:passworddarin. Andere Ports, Zugangsdaten in der Adresse sowie private, lokale und interne Adressen werden abgelehnt (400). Die Portregel gilt beim Setzen einer Adresse: Früher mit einem anderen Port gespeicherte Adressen erhalten weiterhin Zustellungen.events- mindestens ein Ereignis aus der Liste unten; unbekannte Namen werden verworfen.secret- das Signaturgeheimnis. Es wird nur einmal zurückgegeben - speichern Sie es.- Bis zu 10 Adressen pro Konto und getrennt davon bis zu 10 Sandbox-Adressen, die mit Testschlüsseln angelegt wurden; die 11. wird mit
422 business_ruleabgelehnt. - Mit einem Testschlüssel angelegte Adressen sind Sandbox-Adressen: Sie erhalten nur Ereignisse von Sandbox-Aufträgen (Sandbox).
Abonnements lassen sich auch im Kabinett verwalten: API → Webhooks. Dort können Sie auch das Signaturgeheimnis ändern.
Verwaltung
| Anfrage | Was sie tut |
|---|---|
GET /webhooks | Ihre Abonnements (ohne Geheimnisse) |
PATCH /webhooks/{id} | url, events oder status (active / disabled) ändern |
DELETE /webhooks/{id} | Abonnement löschen |
POST /webhooks/{id}/test | Probeereignis senden; Body {"eventType": "content.completed"} bestimmt die Form |
GET /webhooks/{id}/deliveries | die letzten 50 Zustellungen: Status, Antwortcode, Latenz, Antwortanfang |
POST /webhooks/{id}/deliveries/{deliveryId}/resend | Zustellung erneut senden; deliveryId ist die _id einer Zeile aus dem Protokoll |
PATCH ohne ein gültiges Ereignis in events oder mit einem status außer active / disabled wird mit 400 abgelehnt. DELETE eines unbekannten Abonnements antwortet mit 404. Im Zustellprotokoll ist payload das data des Ereignisses.
Ereignisse
Fertige Aufträge
| Ereignis | Wann |
|---|---|
valuation.completed, valuation.failed | vollständiger Bewertungsbericht |
review.completed, review.failed | Berichtsprüfung |
content.completed, content.partial, content.failed | Content-Paket (partial - einige Dateien fehlgeschlagen) |
text.completed, text.failed | Texte |
video.completed, video.failed | Video |
avatar.completed, avatar.failed | Avatar-Video |
photo.completed, photo.failed | Fotoverbesserung |
floorplan.completed, floorplan.failed | Grundriss |
pdf.completed, pdf.failed | PDF-Präsentation |
data: { "jobId", "status", "state" }. status ist der Rohstatus des Werkzeugs in Großbuchstaben (zum Beispiel COMPLETED) - verzweigen Sie nach state. Ist ein Auftrag nach 30 Minuten nicht fertig - .failed mit "reason": "timeout". Ein Auftrag wird nur verfolgt, wenn beim Anlegen ein Abonnement für sein Ereignis bestand (Aufträge und Ergebnisse).
Objekte
| Ereignis | Wann |
|---|---|
object.created | Sie haben ein Objekt erstellt |
object.updated | ein Objekt wurde geändert, auch von einem Moderator |
object.published | ein Objekt wurde öffentlich |
object.deleted | ein Entwurf wurde gelöscht oder ein veröffentlichtes Objekt archiviert |
data: { "objectId", "kind", "status", "occurredAt" }; bei object.deleted statt status - mode (deleted oder archived). Vollständige Daten - GET /objects/{kind}/{id}.
Leads
| Ereignis | Wann |
|---|---|
lead.created | ein neuer Lead an Sie |
lead.updated | Status oder Verantwortlicher geändert |
data: { "leadId", "status", "type", "source", "objectId", "occurredAt" }. Keine personenbezogenen Daten des Kunden im Ereignis - den Lead lesen Sie mit GET /leads/{id}.
Swipe Match
| Ereignis | Wann |
|---|---|
match.lead.offered | Ihnen (oder einem Agenten Ihrer Agentur) wurde ein Lead angeboten |
match.lead.offer_closed | das Angebot wurde ohne Sie geschlossen (Zeit abgelaufen) |
match.lead.matched | der Lead wurde Ihnen zugewiesen |
match.lead.updated | der Lead ist in eine neue Phase gewechselt |
match.lead.unassigned | der Lead wurde Ihnen entzogen |
data: { "leadId", "eventId", "eventCode", "occurredAt", "source", "stage", "active", "leadKind", "clientType", "country", "brokerId", "agencyId" }. Name, Telefon und E-Mail des Kunden sind nie in Ereignissen (Swipe Match).
Zustellformat
POST https://example.com/grem-hook
Content-Type: application/json
x-grem-event: content.completed
x-grem-signature: t=1759400000,v1=3b78a03b78058d229023db2a0b4ee072...
{ "id": "...", "type": "content.completed", "createdAt": "2026-10-02T10:00:00.000Z", "data": { ... } }
idist in allen Wiederholungen einer Zustellung gleich - nutzen Sie es, um ein Ereignis nicht doppelt zu verarbeiten.tundcreatedAtsind der Zeitpunkt des konkreten Versuchs.- Eine Testzustellung (
POST /webhooks/{id}/test) mit einemeventTypeaus der Liste trägttest: trueindata; ohne ihn ist der Ereignistypwebhook.test. EineventType, der nicht in der Liste steht, ist400.webhook.testselbst wird angenommen.
Signatur prüfen
Die Signatur ist ein HMAC-SHA256 über den String "<t>.<roher Body>" mit dem Abonnementgeheimnis, hexadezimal in v1. Prüfen Sie vor jeder Verarbeitung und verwerfen Sie veraltete t (Schutz vor Wiedereinspielung).
Berechnen Sie die Signatur über den rohen Body, genau wie empfangen, vor dem JSON-Parsen. Erneutes Serialisieren ändert die Bytes, und die Signatur stimmt nicht.
const crypto = require('crypto');
function verifyGremSignature(secret, header, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(String(header || '').split(',').map((kv) => kv.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post('/grem-hook', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
if (!verifyGremSignature(process.env.GREM_WEBHOOK_SECRET, req.get('x-grem-signature'), raw)) {
return res.status(400).send('invalid signature');
}
const event = JSON.parse(raw);
// event.id, event.type, event.data
res.sendStatus(200);
});
Im SDK erledigt das verifyWebhookSignature().
Wiederholungen und Deaktivierung
- Eine Zustellung ist erfolgreich, wenn Ihr Server innerhalb von 10 Sekunden mit
2xxantwortet. Antworten Sie schnell, schwere Arbeit im Hintergrund. - Eine fehlgeschlagene Zustellung wird nach 5 Minuten, 30 Minuten, 2 Stunden und 6 Stunden wiederholt - insgesamt bis zu 5 Versuche.
- Nach 5 vollständig fehlgeschlagenen Zustellungen in Folge wird das Abonnement deaktiviert (
status: disabled). Aktivieren Sie es wieder mitPATCH /webhooks/{id}und{"status":"active"}oder im Kabinett. - Das Zustellprotokoll (
GET /webhooks/{id}/deliveries) zeigt den Antwortcode Ihres Servers und den Anfang des Antwortbodys - daran sehen Sie, warum eine Zustellung fehlschlug.
Verwandte Artikel
- Aufträge und Ergebnisse - Auftragszustände.
- Swipe Match - Ereigniscodes der Modul-Leads.