API
Webhooks
Soyez prévenu des événements de vos espaces sur votre propre backend, avec signature.
Donnez à Geta.Team une adresse https, et votre backend est prévenu de chaque événement des espaces de vos clients, dans la minute qui suit.
Le mettre en place
Dans votre dashboard, Compte, section API, Webhook :
- Saisissez votre adresse :
https://uniquement, sur une adresse publique. - Enregistrez, puis copiez votre secret de signature : il n’est affiché qu’une fois. Enregistrer à nouveau l’adresse donne un nouveau secret.
- Envoyez un événement de test : un
pingarrive dans la minute. Les derniers envois et leur statut sont listés en dessous.
Événements
| Événement | Quand |
|---|---|
workspace.ready | L’espace du client est prêt : son lien d’invitation est dans inviteLink |
payment.failed | Un paiement de l’espace a échoué (la carte, le 3D Secure...) |
workspace.suspended | L’espace a été suspendu pour impayé |
workspace.deleted | L’espace a été supprimé (abonnement terminé) |
ping | Un événement de test, depuis le dashboard |
Chaque envoi est en JSON :
{
"id": "evt_8434fff1b5dc162847b6ac3f",
"type": "workspace.ready",
"created": "2026-10-29T10:06:03.000Z",
"data": {
"workspace": {
"id": 77, "clientId": 501, "clientName": "Acme", "clientEmail": "[email protected]",
"status": "running", "url": "https://acme-x.geta.team",
"inviteLink": "https://acme-x.geta.team/invite/...", "employees": 1
}
}
}Champs en plus : paymentUrl et graceHours pour payment.failed ; paymentUrl
pour workspace.suspended ; reason pour workspace.deleted.
Vérifier la signature
Chaque envoi porte :
Geta-Signature: t=1793872803,v1=5f0c...e91
Geta-Event-Id: evt_8434fff1b5dc162847b6ac3f
Geta-Event-Type: workspace.readyv1 est le HMAC-SHA256, en hexadécimal, de t, d’un point, puis du corps
brut, avec votre secret pour clé (toute la chaîne whsec_...). Vérifiez-le sur
le corps brut, avant tout décodage JSON, et refusez un t de plus de 5 minutes
(une requête rejouée).
// Node.js (Express) : app.post("/geta/webhooks", express.raw({ type: "application/json" }), handler)
const crypto = require("crypto");
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const given = Buffer.from(parts.v1 || "", "hex");
return given.length === 32 && crypto.timingSafeEqual(given, Buffer.from(expected, "hex"));
}# Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))// PHP
function verify(string $rawBody, string $header, string $secret): bool {
parse_str(str_replace(',', '&', $header), $parts);
$t = (int)($parts['t'] ?? 0);
if (abs(time() - $t) > 300) return false;
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
return hash_equals($expected, $parts['v1'] ?? '');
}Répondre, nouvelles tentatives
- Répondez n’importe quel statut 2xx rapidement (sous 10 secondes), puis faites le travail.
- Toute autre réponse, ou aucune réponse, est retentée après 1 minute, 5 minutes, 30 minutes, 2 heures, 6 heures et 24 heures, puis abandonnée.
- Un même événement peut arriver deux fois : servez-vous de son
idpour ne faire le travail qu’une fois.