Skip to main content

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

  1. Vous configurez un ou plusieurs endpoints depuis votre espace partenaire Tyllt.
  2. Tyllt envoie une requête POST vers cet endpoint dès qu'un événement souscrit se produit.
  3. Votre serveur répond avec un statut 2xx pour confirmer la réception.

Configurer un endpoint

Depuis votre Espace Tyllt → Paramètres → Webhooks :

  1. Ajoutez une URL HTTPS vers laquelle Tyllt enverra les événements
  2. Sélectionnez les types d'événements à recevoir
  3. 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 ?

  1. Lors de la création du webhook, un secret vous est communiqué une seule fois via l'interface
  2. À chaque appel webhook, nous générons une signature HMAC SHA-1 du payload en utilisant ce secret
  3. 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

ChampTypeDescription
timestampintegerTimestamp Unix de l'événement (en secondes)
dataobjectObjet concerné par l'événement, sérialisé selon les groupes définis pour chaque type
objectNamestringNom de la classe de l'objet concerné : ModelRequestPayment, ModelRequestSecurityDeposit, RequestPayment, RequestSecurityDeposit
typestringType 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 paiement
  • ModelRequestSecurityDeposit : Modèle de demande de dépôt de garantie
  • RequestPayment : Demande de paiement
  • RequestSecurityDeposit : Demande de dépôt de garantie

Types d'événements

Événements communs

TypeDescriptionObjets concernés
creationCréation d'un nouvel objetModelRequestPayment, ModelRequestSecurityDeposit, RequestPayment, RequestSecurityDeposit
updateMise à jour d'un objet existantModelRequestPayment, ModelRequestSecurityDeposit, RequestPayment, RequestSecurityDeposit
deletionSuppression d'un modèleModelRequestPayment, ModelRequestSecurityDeposit

Événements spécifiques aux demandes de paiement

TypeDescriptionObjets concernés
payedPaiement effectué avec succès sur une demandeRequestPayment
cancelDemande annuléeRequestPayment
transaction_failedUne transaction a échoué sur la demandeRequestPayment

Événements spécifiques aux demandes de dépôt de garantie

Cycle de vie de la demande

TypeDescription
cancelDemande annulée
finishDemande terminée
global_errorErreur globale sur la demande de dépôt de garantie
litigationPassage en litige suite à une réclamation sur un encaissement

Transactions et dépôt

TypeDescription
transaction_failedUne transaction a échoué sur la demande
new_transaction_successUne transaction a réussi sur la demande
new_transaction_manual_cancelUne transaction a été annulée manuellement
user_registerUn utilisateur a renseigné ses informations personnelles
deposit_successDépôt de garantie effectué avec succès

États des lieux

TypeDescription
check_inÉtat des lieux d'entrée effectué
check_outÉtat des lieux de sortie effectué

Encaissements (Cash-out)

TypeDescription
cashoutCréation d'une demande d'encaissement
cashout_cancelDemande d'encaissement annulée
cashout_failedDemande d'encaissement échouée (erreur lors de la séquestration du montant)
cashout_reclamationRéclamation émise par le locataire sur la demande d'encaissement
cashout_new_offerNouvelle offre du demandeur sur la demande d'encaissement
cashout_recipient_acceptAcceptation de la demande d'encaissement par le locataire
cashout_owner_deny_reclamationLe demandeur refuse de répondre à la réclamation du locataire
cashout_doneLa 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());
}