DocsAllemagneAfficher la version internationale de la base de connaissances

Webhooks et événements

Dernière mise à jour: 2026-11-067 min de lecture

Un webhook est un POST que GREM envoie à votre URL lorsque quelque chose se produit : une tâche se termine, une annonce ou un lead change. La gestion des abonnements nécessite le scope webhooks et n'est pas facturée. Les webhooks d'une agence se gèrent avec la clé de l'agence entière ; une clé limitée à un agent reçoit 403.

S'abonner

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 - une adresse publique https:// sur le port par défaut (443), sans user:password. Les autres ports, les identifiants dans l'adresse, les adresses privées, locales et internes sont refusés (400). La règle du port s'applique au moment où vous définissez une adresse : les endpoints enregistrés auparavant sur un autre port continuent de recevoir les livraisons.
  • events - au moins un événement de la liste ci-dessous ; les noms inconnus sont ignorés.
  • secret - le secret de signature. Il n'est renvoyé qu'une seule fois - conservez-le.
  • Jusqu'à 10 adresses par compte, et séparément jusqu'à 10 adresses de bac à sable créées avec des clés de test ; la 11e est refusée avec 422 business_rule.
  • Les endpoints créés avec une clé de test sont des endpoints de bac à sable : ils ne reçoivent que les événements des tâches du bac à sable (Bac à sable).

Les abonnements peuvent aussi être gérés dans l'espace personnel : API → Webhooks. Le secret de signature peut y être changé.

Gestion

RequêteCe qu'elle fait
GET /webhooksvos abonnements (sans les secrets)
PATCH /webhooks/{id}modifier url, events ou status (active / disabled)
DELETE /webhooks/{id}supprimer un abonnement
POST /webhooks/{id}/testenvoyer un événement d'exemple ; le corps {"eventType": "content.completed"} définit la forme de l'exemple
GET /webhooks/{id}/deliveriesles 50 dernières livraisons : statut, code de réponse, latence, début de la réponse
POST /webhooks/{id}/deliveries/{deliveryId}/resendrenvoyer une livraison ; deliveryId est le _id d'une ligne du journal

Un PATCH sans aucun événement valide dans events ou avec un status autre que active / disabled est refusé avec 400. Un DELETE d'un abonnement inconnu répond 404. Dans le journal des livraisons, payload correspond au data de l'événement.

Événements

Tâches terminées

ÉvénementQuand
valuation.completed, valuation.failedrapport d'évaluation complet
express.completed, express.failedestimation express lancée avec Prefer: respond-async
rent.completed, rent.failedestimation du loyer lancée avec Prefer: respond-async
draft.completed, draft.failedbrouillon d’annonce lancé avec Prefer: respond-async
site.completed, site.failedpage du bien lancée avec Prefer: respond-async
review.completed, review.failedvérification de rapport
review.action_requiredla vérification de rapport a lu le document et attend confirm
content.completed, content.partial, content.failedpack de contenu (partial - certains fichiers ont échoué)
text.completed, text.failedtextes
video.completed, video.failedvidéo
avatar.completed, avatar.failedvidéo d'avatar
photo.completed, photo.failedamélioration de photos
floorplan.completed, floorplan.failedplan
pdf.completed, pdf.failedprésentation PDF

data : { "jobId", "status", "state" }. status est le statut brut de l'outil en majuscules (par exemple COMPLETED) - basez vos branchements sur state. Si une tâche ne s'est pas terminée en 30 minutes - .failed avec "reason": "timeout". Une tâche n'est suivie que si un abonnement à son événement existait au moment de sa création (Tâches et résultats). Dans les événements express.* et rent.*, status vaut DONE ou FAILED. Pour un plan ou une vérification de rapport lancés avec Prefer: respond-async, l'événement porte l'identifiant de la tâche de requête (areq_…) ; review.completed d'une telle tâche signifie que le document est lu et nécessite confirm - la vérification elle-même envoie son propre événement après confirmation, comme d'habitude.

review.action_required arrive une fois, quand une vérification de rapport a lu le document et attend confirm : jobId est la vérification elle-même (aussi lancée avec Prefer: respond-async), status vaut NEED_CONFIRM, state vaut action_required.

Annonces

ÉvénementQuand
object.createdvous avez créé une annonce
object.updatedune annonce a changé, y compris par un modérateur
object.publishedune annonce est devenue publique
object.deletedun brouillon a été supprimé ou une annonce publiée archivée

data : { "objectId", "kind", "status", "occurredAt" } ; object.deleted porte mode (deleted ou archived) au lieu de status. Données complètes - GET /objects/{kind}/{id}.

Leads

ÉvénementQuand
lead.createdun nouveau lead qui vous est adressé
lead.updatedle statut ou le responsable a changé

data : { "leadId", "status", "type", "source", "objectId", "occurredAt" }. Aucune donnée personnelle du client dans l'événement - lisez le lead complet avec GET /leads/{id}.

Swipe Match

ÉvénementQuand
match.lead.offeredun lead vous a été proposé (ou à un agent de votre agence)
match.lead.offer_closedl'offre s'est fermée sans vous (le délai a expiré)
match.lead.matchedle lead vous a été attribué
match.lead.updatedle lead est passé à une nouvelle étape
match.lead.unassignedle lead vous a été retiré

data : { "leadId", "eventId", "eventCode", "occurredAt", "source", "stage", "active", "leadKind", "clientType", "country", "brokerId", "agencyId" }. Le nom, le téléphone et l'e-mail du client ne figurent jamais dans les événements (Swipe Match).

Format de livraison

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": { ... } }
  • id est identique dans toutes les nouvelles tentatives d'une même livraison - utilisez-le pour éviter de traiter un événement deux fois.
  • t et createdAt correspondent à l'heure de la tentative concernée.
  • Une livraison de test (POST /webhooks/{id}/test) avec un eventType de la liste porte test: true dans data ; sans lui, le type d'événement est webhook.test. Un eventType absent de la liste donne 400. webhook.test lui-même est accepté.

Vérifier la signature

La signature est un HMAC-SHA256 de la chaîne "<t>.<raw body>" calculé avec le secret de l'abonnement, encodé en hexadécimal dans v1. Vérifiez-la avant tout traitement et rejetez les valeurs t trop anciennes (protection contre le rejeu).

Calculez la signature sur le corps brut de la requête, exactement tel que reçu, avant de parser le JSON. Une re-sérialisation modifie les octets et la signature ne correspondra pas.

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);
});

Dans le SDK, verifyWebhookSignature() fait la même chose.

Nouvelles tentatives et désactivation

  • Une livraison réussit lorsque votre serveur répond 2xx en moins de 10 secondes. Répondez vite et effectuez le travail lourd en arrière-plan.
  • Une livraison échouée est retentée après 5 minutes, 30 minutes, 2 heures et 6 heures - jusqu'à 5 tentatives au total.
  • Après 5 livraisons consécutives totalement échouées, l'abonnement est désactivé (status: disabled). Réactivez-le avec PATCH /webhooks/{id} et {"status":"active"}, ou dans l'espace personnel.
  • Le journal des livraisons (GET /webhooks/{id}/deliveries) affiche le code de réponse de votre serveur et le début de son corps - c'est là que vous voyez pourquoi une livraison a échoué.

Voir aussi

Cet article vous a-t-il aidé ?