Webhooks
Les webhooks permettent à votre serveur de recevoir une notification dès qu'un événement se produit côté Tyllt — sans avoir à interroger l'API en boucle. Vous déclarez une URL, Tyllt lui envoie une requête POST à chaque événement pertinent.
Comment ça fonctionne
- Vous configurez un ou plusieurs endpoints depuis votre espace partenaire Tyllt.
- Tyllt envoie une requête
POSTvers cet endpoint dès qu'un événement souscrit se produit. - Votre serveur répond avec un statut
2xxpour confirmer la réception.
Configurer un endpoint
Depuis votre Espace Tyllt → Paramètres → Webhooks :
- Ajoutez une URL HTTPS vers laquelle Tyllt enverra les événements
- Sélectionnez les types d'événements à recevoir
- Configurez l'authentification (optionnel) : vous pouvez définir une clé API qui sera ajoutée dans les headers de chaque requête
Authentification par clé API
Pour sécuriser vos webhooks, vous pouvez configurer une authentification simple par clé API. Cette clé sera ajoutée dans les en-têtes HTTP de chaque appel webhook.
Paramètres configurables :
- Nom du header : Le nom de l'en-tête HTTP (ex:
X-API-Key,Authorization, etc.) - Valeur de la clé : La clé secrète que vous définirez
Exemple de configuration :
- Header :
X-Tyllt-Webhook-Key - Valeur :
votre_cle_secrete_ici
Tyllt ajoutera alors cet en-tête à chaque requête webhook :
POST /webhooks/tyllt HTTP/1.1 Host: votreserveur.com Content-Type: application/json X-Tyllt-Webhook-Key: votre_cle_secrete_ici
Vous pouvez configurer plusieurs endpoints (par exemple un par environnement) et les activer ou désactiver indépendamment.
Format des payloads
Chaque événement est envoyé en POST avec un corps JSON de cette forme :
{
"timestamp": 1753693200,
"data": {
"id": "req_92a1c4d5",
"name": "Dépôt de garantie - Appartement Paris 15",
"status": "in_progress",
"depositAmount": 1500,
"recipient": {
"email": "locataire@example.com"
}
},
"objectName": "RequestSecurityDeposit",
"type": "creation"
}
Signature des webhooks
Pour garantir la sécurité et l'authenticité des webhooks, chaque requête POST envoyée par notre plateforme contient un header X-WEBHOOK-SIGNATURE que vous devez vérifier.
Comment ça fonctionne ?
- Lors de la création du webhook, un secret vous est communiqué une seule fois via l'interface
- À chaque appel webhook, nous générons une signature HMAC SHA-1 du payload en utilisant ce secret
- Votre serveur doit recalculer cette signature et la comparer avec celle reçue dans le header
⚠️ Important Le secret n'est affiché qu'une seule fois lors de la création du webhook. Conservez-le précieusement dans un endroit sécurisé (variables d'environnement, gestionnaire de secrets, etc.). ⚠️
Vérification de la signature (PHP)
Voici comment vérifier la signature d'un webhook dans votre application :
<?php
// 1. Récupérer la signature envoyée par Tyllt
$signature = $request->headers->get('X-WEBHOOK-SIGNATURE');
// 2. Récupérer le payload brut de la requête
$payload = json_encode($request->request->all());
// 3. Calculer la signature attendue avec votre secret
$secret = 'votre_webhook_secret'; // Secret obtenu lors de la création du webhook
$expectedHash = hash_hmac('sha1', $payload, $secret);
// 4. Comparer les signatures de manière sécurisée
$isValid = hash_equals($expectedHash, $signature);
if ($isValid) {
// ✅ La signature est valide, traiter le webhook
echo "Webhook authentifié avec succès";
} else {
// ❌ Signature invalide, rejeter la requête
http_response_code(401);
echo "Signature invalide";
exit;
}
Structure du payload
| Champ | Type | Description |
|---|---|---|
timestamp | integer | Timestamp Unix de l'événement (en secondes) |
data | object | Objet concerné par l'événement, sérialisé selon les groupes définis pour chaque type |
objectName | string | Nom de la classe de l'objet concerné : ModelRequestPayment, ModelRequestSecurityDeposit, RequestPayment, RequestSecurityDeposit |
type | string | Type d'événement (voir liste ci-dessous) |
Objets supportés
Les webhooks sont actuellement disponibles pour les objets suivants :
ModelRequestPayment: Modèle de demande de paiementModelRequestSecurityDeposit: Modèle de demande de dépôt de garantieRequestPayment: Demande de paiementRequestSecurityDeposit: Demande de dépôt de garantie
Types d'événements
Événements communs
| Type | Description | Objets concernés |
|---|---|---|
creation | Création d'un nouvel objet | ModelRequestPayment, ModelRequestSecurityDeposit, RequestPayment, RequestSecurityDeposit |
update | Mise à jour d'un objet existant | ModelRequestPayment, ModelRequestSecurityDeposit, RequestPayment, RequestSecurityDeposit |
deletion | Suppression d'un modèle | ModelRequestPayment, ModelRequestSecurityDeposit |
Événements spécifiques aux demandes de paiement
| Type | Description | Objets concernés |
|---|---|---|
payed | Paiement effectué avec succès sur une demande | RequestPayment |
cancel | Demande annulée | RequestPayment |
transaction_failed | Une transaction a échoué sur la demande | RequestPayment |
Événements spécifiques aux demandes de dépôt de garantie
Cycle de vie de la demande
| Type | Description |
|---|---|
cancel | Demande annulée |
finish | Demande terminée |
global_error | Erreur globale sur la demande de dépôt de garantie |
litigation | Passage en litige suite à une réclamation sur un encaissement |
Transactions et dépôt
| Type | Description |
|---|---|
transaction_failed | Une transaction a échoué sur la demande |
new_transaction_success | Une transaction a réussi sur la demande |
new_transaction_manual_cancel | Une transaction a été annulée manuellement |
user_register | Un utilisateur a renseigné ses informations personnelles |
deposit_success | Dépôt de garantie effectué avec succès |
États des lieux
| Type | Description |
|---|---|
check_in | État des lieux d'entrée effectué |
check_out | État des lieux de sortie effectué |
Encaissements (Cash-out)
| Type | Description |
|---|---|
cashout | Création d'une demande d'encaissement |
cashout_cancel | Demande d'encaissement annulée |
cashout_failed | Demande d'encaissement échouée (erreur lors de la séquestration du montant) |
cashout_reclamation | Réclamation émise par le locataire sur la demande d'encaissement |
cashout_new_offer | Nouvelle offre du demandeur sur la demande d'encaissement |
cashout_recipient_accept | Acceptation de la demande d'encaissement par le locataire |
cashout_owner_deny_reclamation | Le demandeur refuse de répondre à la réclamation du locataire |
cashout_done | La demande d'encaissement est terminée |
Bonnes pratiques
Sécurité
- Vérifiez l'authentification : Si vous avez configuré une clé API, vérifiez toujours sa présence et sa validité avant de traiter un événement
- Utilisez HTTPS : Seules les URLs HTTPS sont acceptées pour garantir la confidentialité des données
Performance et fiabilité
- Répondez vite : Accusez réception avec un statut 200 OK avant de traiter l'événement
- Traitez de façon asynchrone : Utilisez une file d'attente pour traiter les événements de manière asynchrone
- Dédupliquez : Un même événement peut être livré plusieurs fois ; utilisez le champ timestamp et data.id pour ignorer les doublons
- Gérez les erreurs : Implémentez une gestion d'erreur robuste avec des logs détaillés
Compatibilité
- Restez tolérant au format : De nouveaux champs peuvent être ajoutés aux payloads ; ignorez ceux que vous ne reconnaissez pas
- Ne vous fiez pas à l'ordre : Les webhooks peuvent arriver dans un ordre différent de celui des événements
Exemple d'implémentation
PHP
<?php
// Vérifier la clé API
$apiKey = $_SERVER['HTTP_X_TYLLT_WEBHOOK_KEY'] ?? '';
$expectedKey = getenv('TYLLT_WEBHOOK_KEY');
if ($apiKey !== $expectedKey) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
// Accuser réception immédiatement
http_response_code(200);
echo json_encode(['received' => true]);
// Récupérer le payload
$payload = json_decode(file_get_contents('php://input'), true);
// Forcer l'envoi de la réponse au client
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
// Traiter l'événement de façon asynchrone
$timestamp = $payload['timestamp'];
$data = $payload['data'];
$objectName = $payload['objectName'];
$type = $payload['type'];
try {
// Déduplication
$eventId = "{$objectName}_{$data['id']}_{$type}_{$timestamp}";
if (isEventProcessed($eventId)) {
error_log("Event {$eventId} already processed");
exit;
}
// Traitement selon le type
switch ($type) {
case 'creation':
handleCreation($objectName, $data);
break;
case 'payed':
handlePayment($data);
break;
case 'deposit_success':
handleDepositSuccess($data);
break;
// ... autres cas
}
markEventAsProcessed($eventId);
} catch (Exception $e) {
error_log("Error processing webhook: " . $e->getMessage());
}