Guide de l’API Palisade

Premiers pas avec l’API Palisade. Vous cherchez le détail des points de terminaison? Consultez la référence de l’API. Vous cherchez plutôt la documentation de l’application? La documentation du produit Palisade couvre le tableau de bord, la configuration des domaines et le déploiement de DMARC.

Authentification

L’API Palisade authentifie les requêtes au moyen de clés d’API. Créez et gérez vos clés à partir du tableau de bord — ouvrez le menu de votre avatar dans le coin supérieur droit et choisissez Clés d’API — avec n’importe quel forfait, ou obtenez-en une par programmation avec l’inscription sans interface.

Important : vos clés d’API donnent un accès étendu à votre compte; gardez-les donc en sécurité. Ne communiquez pas vos clés secrètes dans des dépôts publics, du code côté client ou tout autre endroit non sécurisé.

Toutes les requêtes à l’API doivent passer par HTTPS. Les requêtes envoyées en HTTP simple sont refusées. À part un petit nombre de points de terminaison publics (comme cette documentation et la spécification OpenAPI), toute requête qui ne contient pas d’identifiants valides échoue. Les sessions du tableau de bord s’authentifient avec un JWT Bearer plutôt qu’avec une clé d’API.

La clé d’API sert de nom d’utilisateur dans l’authentification HTTP Basic, avec un mot de passe vide :

curl https://api.palisade.email/domains \
  -u "$PALISADE_API_KEY:" \
  -H "Palisade-Version: 2025-04-30"

Inscription sans interface

Créez un compte Palisade entièrement au moyen de l’API — aucun navigateur requis. Cette fonction est conçue pour les agents d’IA et les automatisations qui contrôlent une boîte de réception.

1. Commencez l’inscription avec une adresse courriel. Les adresses professionnelles et personnelles sont toutes deux acceptées; les domaines jetables sont refusés avec un 422 (disposable_email_domain). Aucun captcha n’est requis. Le point de terminaison a une limite de débit par adresse IP; respectez donc les en-têtes de réponse RateLimit-*. Chaque adresse courriel est aussi limitée séparément à un petit nombre de codes par heure; au-delà, la réponse est un 429 (signup_email_throttled) :

curl -X POST https://api.palisade.email/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "ops@example.com", "organization_name": "Example Corp"}'

{"signup_id": "<uuid>", "expires_in_seconds": 900}

2. Un code à six chiffres est envoyé à la boîte de réception. Vérifiez-le dans les 15 minutes et en 5 tentatives erronées au plus; au-delà de l’une ou l’autre limite, l’inscription est morte et chaque appel suivant renvoie signup_challenge_invalid; commencez-en donc une nouvelle :

curl -X POST https://api.palisade.email/signup/verify \
  -H "Content-Type: application/json" \
  -d '{"signup_id": "<uuid>", "code": "123456"}'

La réponse contient votre organisation et une clé d’API affichée une seule fois — conservez-la en lieu sûr. Le compte est une organisation Palisade complète : la même adresse courriel pourra plus tard se connecter au tableau de bord en réinitialisant le mot de passe.

Ensuite, le parcours habituel est le suivant : POST /domains pour ajouter un domaine, GET /domains/{id}/dns-records pour obtenir les enregistrements DNS exacts à publier chez votre fournisseur DNS, puis POST /domains/{id}/verify jusqu’à ce que chaque enregistrement indique verified.

Serveur MCP

Palisade offre un serveur Model Context Protocol natif pour que les agents d’IA puissent piloter la plateforme directement. Il expose les principaux flux de travail de Palisade sous forme d’outils, limités à l’organisation à laquelle l’appelant s’est connecté.

Fonctionnement de la connexion

Il n’y a rien à créer d’abord. Un client qui appelle le point de terminaison sans identifiants reçoit un 401 portant un défi RFC 9728 qui nomme les métadonnées de la ressource protégée :

WWW-Authenticate: Bearer resource_metadata="https://api.palisade.email/.well-known/oauth-protected-resource"

Ces métadonnées nomment le serveur d’autorisation. Chaque client que vous configurez vous-même se connecte avec le même client OAuth public, ryKtuiPypMeYMoL1Cmhxtz6BYrEYQbLV, qui n’a pas de secret (PKCE). Le navigateur ouvre la page de connexion de Palisade, vous choisissez une organisation, et le client obtient un jeton limité à celle-ci et à votre rôle; un lecteur ne peut donc rien modifier au moyen de MCP. Les connexions que vous avez autorisées sont énumérées sous API et MCP à app.palisade.email. Tous les assistants qui se connectent avec ce client partagent une même connexion, qui y figure avec les assistants qui l’ont utilisée; la révoquer les déconnecte donc tous, dans chaque organisation dont vous êtes membre. Un jeton qu’un assistant détient déjà continue de fonctionner jusqu’à son expiration.

Un client qui inscrit son propre client OAuth est refusé. L’inscription dynamique de client et les documents de métadonnées d’identifiant de client produisent tous deux un client auquel le serveur d’autorisation ne peut pas rattacher d’organisation; le point de terminaison répond donc 403 et nomme le client à utiliser à la place.

Connecteurs personnalisés de claude.ai

Dans claude.ai, ouvrez Settings, puis Connectors, puis Add custom connector :

Laissez de côté l’option que recommande la boîte de dialogue, Use Anthropic’s hosted client metadata, ainsi que register one automatically : il s’agit dans les deux cas de clients qui s’inscrivent eux-mêmes, et Palisade les refuse.

Claude Code

Une seule commande, qui porte l’identifiant de client et un port de rappel fixe, parce que le serveur d’autorisation enregistre des URI de redirection exacts :

claude mcp add --transport http --client-id ryKtuiPypMeYMoL1Cmhxtz6BYrEYQbLV --callback-port 8765 palisade https://api.palisade.email/mcp

Codex et les autres clients stdio

Les clients qui exécutent des serveurs stdio, dont Codex, Cursor et Windsurf, utilisent la passerelle publiée, qui enveloppe le serveur distant avec mcp-remote et se connecte avec le même client. Rien à exporter :

codex mcp add palisade -- npx -y @palisadeemail/mcp

La même chose sous forme de configuration de client :

{
  "mcpServers": {
    "palisade": {
      "command": "npx",
      "args": ["-y", "@palisadeemail/mcp"]
    }
  }
}

Outils

Chaque outil agit sur l’organisation à laquelle vous vous êtes connecté, sauf les deux lectures du DNS en vigueur, qui prennent un simple nom de domaine et lisent le DNS public de n’importe quel domaine, qu’il figure dans le compte ou non.

Le parcours idéal pour intégrer un domaine : create_domain → get_dns_records → publier les enregistrements chez votre fournisseur DNS → verify_domain → surveiller list_tasks et les rapports → complete_task une fois une correction vérifiée.

ChatGPT

Palisade prend en charge ChatGPT sur le point de terminaison principal https://api.palisade.email/mcp, avec le même client OAuth publié qu’utilise claude.ai. Activez Developer mode sous Settings → Plugins, ouvrez Plugins dans la barre latérale et choisissez Create app, collez le point de terminaison, choisissez OAuth, puis, sous Advanced OAuth settings, réglez Registration method à User-Defined OAuth Client et entrez l’identifiant de client sans secret. Les deux autres méthodes d’inscription font inscrire un client par lui-même et sont refusées; voir plus haut. Palisade ne figure pas dans le répertoire d’applications d’OpenAI, et la présence dans ce répertoire est distincte de la prise en charge de ChatGPT.

Microsoft 365 Copilot

L’administrateur Microsoft 365 de votre organisation doit installer l’agent Palisade dans votre locataire Microsoft 365 et donner accès à votre compte professionnel. Les administrateurs peuvent communiquer avec l’équipe de soutien de Palisade pour obtenir le paquet d’installation et de l’aide pour la configuration. La distribution est gérée par votre organisation; Palisade ne figure pas dans le Microsoft Agent Store public. L’accès dépend des politiques de votre locataire et de vos licences Copilot.

  1. Ouvrez Microsoft 365 Copilot avec le compte professionnel que votre administrateur a autorisé, puis sélectionnez l’agent Palisade.
  2. Demandez List my Palisade domains.
  3. Lorsque vous y êtes invité, connectez-vous à Palisade et choisissez votre organisation Palisade.
  4. Confirmez que les domaines renvoyés appartiennent à cette organisation.

Votre locataire Microsoft 365 détermine qui peut ouvrir l’agent. L’organisation Palisade et le rôle que vous avez choisis déterminent les données et les actions auxquelles il a accès. L’agent installé comprend ses paramètres de connexion; vous n’avez besoin ni d’une clé d’API, ni d’un identifiant de client OAuth, ni d’un secret de client.

Si l’agent est absent, demandez à votre administrateur Microsoft 365 de vérifier l’installation et l’attribution de votre compte. Si la connexion réussit, mais que les domaines n’apparaissent pas, utilisez Chat settings → Agents → Palisade → Sign out, puis rouvrez l’agent et connectez-vous de nouveau. Si le problème persiste, communiquez avec l’équipe de soutien en indiquant l’erreur et l’heure de la tentative; n’envoyez ni jetons ni secrets.

Pour déconnecter Copilot, ouvrez API et MCP → Assistants connectés dans Palisade et révoquez sa connexion dédiée. Son libellé est le nom d’application Palisade configuré, qui peut être différent du nom de l’agent. Cela ne révoque pas la connexion Palisade partagée qu’utilisent les autres assistants. Un jeton d’accès déjà émis reste valide jusqu’à son expiration.

Point de terminaison de répertoire en lecture seule

https://api.palisade.email/mcp/directory est un deuxième point de terminaison, en lecture seule, qui offre 14 des outils énumérés ci-dessus — surveillance et diagnostic seulement, sans modification ni action de facturation. Il existe pour les clients qui joignent Palisade par un répertoire d’applications public, où la connexion est établie avant que quiconque ait choisi une organisation.

Il diffère de https://api.palisade.email/mcp par la façon dont il détermine votre organisation. Il utilise toujours l’organisation de votre jeton lorsqu’il y en a une; ce qu’il ajoute, c’est une solution de repli lorsqu’il n’y en a pas : il recherche votre compte et prend l’unique organisation qu’il trouve. Ce repli ne fonctionne que si la réponse est sans ambiguïté; un compte qui n’appartient à aucune organisation active reçoit donc un 403, tout comme un compte qui appartient à plus d’une organisation, parce que la connexion ne peut pas choisir sans risque. Le point de terminaison principal n’a pas de repli et refuse simplement un jeton sans organisation.

Pour un jeton OAuth sans organisation, la connexion de répertoire standard exige les quatre autorisations domains:read, groups:read, tasks:read et dmarc_reports:read. Palisade reconnaît aussi les propres clients OAuth de ChatGPT, c’est-à-dire les clients tiers qui ne peuvent terminer la connexion qu’en redirigeant vers https://chatgpt.com : quand l’un d’eux ne reçoit que offline_access et aucune autorisation d’API, le point de terminaison de répertoire déduit un accès en lecture du rôle de l’utilisateur dans son unique organisation active. Cette exception est propre à ces clients; les autres connexions sans organisation qui n’ont pas les quatre autorisations de lecture reçoivent un 401 qui nomme les autorisations requises dans l’en-tête WWW-Authenticate :

WWW-Authenticate: Bearer resource_metadata="https://api.palisade.email/.well-known/oauth-protected-resource/mcp/directory", scope="domains:read groups:read tasks:read dmarc_reports:read"

Vous avez besoin d’une organisation Palisade avant de vous connecter. Chaque outil MCP agit sur une organisation; connectez-vous donc à app.palisade.email (ou créez un compte avec l’inscription sans interface) avant d’ajouter le connecteur. Une connexion sans organisation renvoie un 403 qui indique où en créer une.

Point de terminaison d’audit public

https://api.palisade.email/mcp/public est un troisième point de terminaison qui ne demande ni compte, ni organisation, ni identifiants. Il n’offre que les deux outils qui lisent le DNS public de n’importe quel domaine : audit_domain, qui note sur 100 l’ensemble de la configuration SPF/DKIM/DMARC/BIMI/MTA-STS d’un domaine, et validate_spf_include, qui calcule ce que coûte un include SPF par rapport à la limite de 10 recherches avant que vous l’ajoutiez. Ni l’un ni l’autre ne lit quoi que ce soit qui appartient à Palisade; il n’y a donc aucune organisation à laquelle les limiter, et des identifiants envoyés à ce point de terminaison sont ignorés plutôt que vérifiés.

C’est l’équivalent MCP du point de terminaison REST anonyme GET /tools/audit. Sa limite de débit s’applique par adresse de client plutôt que par identité, dans une catégorie DNS qui lui est propre, dimensionnée comme la portée dns décrite sous Limites de débit, mais non partagée avec elle; un appel au-delà de la limite revient sous forme d’erreur d’outil dont l’error.scope est mcp_dns et qui porte retry_after_seconds. Tout client qui parle Streamable HTTP s’y connecte sans rien configurer :

claude mcp add --transport http palisade-audit https://api.palisade.email/mcp/public

Tout le reste — la surveillance, les enregistrements à publier, DMARC hébergé, les rapports et les billets — se trouve sur https://api.palisade.email/mcp et exige une organisation Palisade.

Erreurs

Palisade utilise les codes de réponse HTTP standard pour indiquer si une requête à l’API a réussi ou non.

Les erreurs des points de terminaison de la référence de l’API sont des corps JSON qui portent un code en minuscules sur lequel vous pouvez vous fonder et un message rédigé pour une personne; certaines ajoutent un param qui nomme le champ fautif ou un objet details qui donne plus de précisions. L’entrée de chaque point de terminaison dans la référence documente les formes d’erreur qu’il peut renvoyer. Deux erreurs sont renvoyées avant qu’un point de terminaison soit atteint et ont donc la même forme dans toute l’API : une requête sans identifiants de clé d’API valides reçoit un 401 avec code: "unauthorized" et un en-tête WWW-Authenticate, et un chemin qui ne correspond à aucune route reçoit un 404 avec code: "not_found" et, sous details, des liens vers ce guide et vers le document OpenAPI, pour qu’un client qui a deviné une URL puisse trouver la bonne :

{
  "code": "not_found",
  "message": "No API route matches this request.",
  "details": {
    "documentation": "https://developer.palisade.email/docs/guide",
    "openapi": "https://api.palisade.email/swagger.json"
  }
}

Le 401 a la même forme. Son message indique si aucun identifiant n’a été envoyé ou si la clé n’a pas été acceptée, details.documentation pointe vers Authentification, et l’en-tête WWW-Authenticate nomme les deux mécanismes que l’API accepte : Basic pour une clé d’API et Bearer pour un jeton de session du tableau de bord. Une requête faite par un navigateur avec fetch reçoit un défi Bearer seulement, parce qu’un défi Basic ferait ouvrir au navigateur sa propre boîte de dialogue de connexion par-dessus l’application.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="palisade", Bearer realm="palisade"

{
  "code": "unauthorized",
  "message": "The API key is not valid, has been rotated, or has been revoked.",
  "details": {
    "documentation": "https://developer.palisade.email/docs/guide#authentication"
  }
}

Un client qui envoie Accept: text/plain ou Accept: text/html reçoit plutôt la ligne d’état en texte brut pour l’une ou l’autre erreur, ce que les deux renvoyaient avant que ce contrat existe; tout le reste, y compris l’absence d’en-tête Accept, reçoit le JSON.

Versions

Chaque version majeure de Palisade peut comporter des changements non rétrocompatibles; la mise à niveau pourrait donc exiger des modifications à votre code existant. La version majeure actuelle est 2025-04-30. Il n’y a pour l’instant aucun calendrier de publication fixe, mais nous nous engageons à maintenir les versions majeures pendant au moins un an.

Les clients qui utilisent une clé d’API doivent indiquer explicitement la version de l’API dans chaque requête, au moyen de l’en-tête Palisade-Version; toute requête avec une clé d’API qui ne contient pas de version valide échoue. Les sessions du tableau de bord (JWT) utilisent la plus récente version par défaut quand l’en-tête est omis.

Limites de débit

L’API applique des catégories de limite de débit indépendantes : une catégorie globale pour chaque requête, une catégorie par clé d’API pour chaque requête authentifiée avec une clé d’API, et des catégories DNS (dont une plus stricte par clé d’API) pour les points de terminaison qui déclenchent une résolution DNS sortante. Chaque réponse porte les en-têtes RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset; les réponses 429 ajoutent Retry-After. Respectez les en-têtes — ne codez pas les limites en dur. Le contrat complet est documenté dans l’introduction de la référence de l’API.

Pagination

Toutes les ressources de premier niveau de l’API Palisade permettent la récupération en lot au moyen de méthodes de « liste ». Par exemple, vous pouvez lister les domaines, les groupes ou les billets. Ces méthodes de liste suivent une structure uniforme et acceptent au moins deux paramètres : page et per_page.

Chaque méthode de « liste » renvoie l’état actuel de la pagination dans le champ page_info. Ce champ comprend aussi le nombre total de ressources qui correspondent aux filtres fournis.

Développer les réponses

Palisade vous permet de demander des détails supplémentaires dans les réponses de l’API au moyen du paramètre de requête expand. Ce paramètre fonctionne avec toutes les requêtes à l’API et ne touche que la réponse à cette requête précise.

Beaucoup d’objets de Palisade ne renvoient que l’identifiant des objets liés. Par exemple, un domaine peut comporter l’identifiant d’un groupe. Avec le paramètre expand, vous pouvez demander à Palisade d’inclure l’objet groupe complet plutôt que son seul identifiant. Les champs qui peuvent être développés sont indiqués dans le paramètre expand.

Certains champs, comme les données sensibles ou facultatives, ne sont pas inclus par défaut. Vous pouvez aussi les demander avec le paramètre expand.

Vous ne pouvez utiliser expand qu’avec des requêtes d’obtention ou de liste, et vous pouvez développer plusieurs champs à la fois en incluant plusieurs éléments dans le tableau expand. Gardez à l’esprit que développer en profondeur les réponses de liste peut ralentir les choses.

Webhooks

Plutôt que d’interroger l’API à intervalles réguliers pour repérer les changements, vous pouvez inscrire un point de terminaison HTTPS, et Palisade lui enverra un événement par POST chaque fois que quelque chose se produit dans votre compte. Gérez les points de terminaison sous /webhook-endpoints; le catalogue des événements est disponible à GET /webhook-events.

Catalogue des événements

Le tableau ci-dessous est généré à partir du même catalogue que celui selon lequel l’émetteur valide et que sert GET /webhook-events; il ne peut donc pas s’écarter de ce que Palisade envoie réellement. Les noms d’événements ne font que s’ajouter : de nouveaux noms apparaissent, mais les noms existants ne sont jamais renommés ni réaffectés. Traitez un type inconnu comme quelque chose à ignorer plutôt que comme une erreur.

ÉvénementDéclenché quanddata.object
domain.createdUn domaine a été ajouté à l’organisation.domain
domain.updatedUn domaine a changé. Se déclenche lors des transitions d’état de surveillance, des changements de score et des mises à jour de configuration.domain
domain.deletedUn domaine a été retiré de l’organisation.domain
domain.monitoring.activatedUn domaine a atteint la surveillance DMARC complète pour la première fois. L’atteindre de nouveau après une interruption correspond à domain.monitoring.recovered.domain
domain.monitoring.failedLa surveillance n’a pas pu être établie ou a été perdue. L’objet domaine porte monitoring_failure_reason, qui indique si l’enregistrement est absent, se trouve seulement sur le parent, n’a pas le rua de Palisade, et ainsi de suite.domain
domain.monitoring.driftedUn domaine qui était configuré ne résout plus les enregistrements que Palisade attend.domain
domain.monitoring.recoveredUn domaine est revenu à la surveillance complète après une dérive ou un échec.domain
domain.hosted_record.readyUn enregistrement hébergé par Palisade est en service. data.record indique lequel : dmarc, spf, dkim, bimi ou mta_sts.domain
domain.hosted_record.errorUn enregistrement hébergé par Palisade est en erreur. data.record indique lequel. Le rétablissement est détecté par un rapprochement horaire; l’événement ready correspondant peut donc arriver une heure ou plus en retard.domain
domain.score_changedUn domaine a franchi une tranche de score — critical, bad, good ou great — plutôt que de simplement gagner ou perdre un point. La nouvelle tranche est score_band sur l’objet domaine; data.previous_attributes porte l’ancien score et l’ancienne tranche.domain
domain.policy_changedLa politique DMARC du domaine a changé, par exemple de none à quarantine, puis à reject. data.previous_attributes porte l’ancienne politique.domain
mta_sts.enabledMTA-STS hébergé par Palisade a été activé pour un domaine.mta_sts
mta_sts.updatedUne politique MTA-STS a été modifiée, par exemple son mode ou ses hôtes MX.mta_sts
mta_sts.disabledMTA-STS hébergé par Palisade a été désactivé pour un domaine.mta_sts
task.openedUn billet de correction a été ouvert et est resté ouvert pendant toute la période de stabilisation. Pas en temps réel : consultez les notes sur les délais de livraison dans le guide de l’API.task
task.completedUn billet a été résolu.task
task.canceledPalisade a déterminé que le billet ne s’applique plus et l’a fermé. Distinct de task.dismissed, qui correspond à une personne qui le rejette.task
task.dismissedQuelqu’un a écarté le billet. Distinct de task.canceled, qui correspond à Palisade qui le ferme automatiquement.task
task.reopenedUn billet qui avait été résolu ou annulé est revenu. Abonnez-vous à cet événement en plus de task.completed; sinon, un billet fermé à l’achèvement n’apprendra jamais que le travail est revenu.task
organization.updatedLes renseignements ou les paramètres de l’organisation ont changé.organization
member.addedUn compte Palisade existant a été ajouté à l’organisation.member
member.updatedLes rôles d’un membre ou son accès aux groupes ont changé.member
member.removedUn membre a été retiré de l’organisation.member
invitation.createdQuelqu’un a été invité à se joindre à l’organisation.invitation
invitation.revokedUne invitation en attente a été retirée. Il n’y a délibérément aucun événement invitation.accepted : l’acceptation se fait dans Auth0, et Palisade ne peut pas l’observer en temps réel pour l’instant.invitation
group.createdUn groupe a été créé.group
group.updatedUn groupe a été renommé ou ses paramètres ont changé.group
group.deletedUn groupe a été supprimé.group

Les événements TLS-RPT ne figurent pas dans le catalogue. Les rapports sont ingérés par un pipeline distinct qui n’a aucun canal de retour vers l’API; ce service n’a donc rien à observer. Interrogez plutôt GET /domains/{domainId}/tls-reports.

Charges utiles des événements

Chaque livraison a la même enveloppe. data.object est la ressource touchée, sous la forme que renvoie l’API REST. Pour les événements qui concernent quelque chose qui n’existe plus, c’est la ressource telle qu’elle était juste avant sa suppression. Le seul cas qui peut être plus mince est invitation.revoked : une invitation supprimée au moyen de l’identifiant brut du fournisseur n’est jamais relue; cette charge utile ne porte donc que {id, object}. Une suppression au moyen de l’identifiant que renvoie GET /invitations — ce que fait l’API — donne l’objet complet.

{
  "id": "evt_9f2c1b7d8e4a5c3b0d6f1a2e3c4b5d6e",
  "type": "domain.updated",
  "api_version": "2025-04-30",
  "created": "2026-08-11T14:02:11.482Z",
  "data": { "object": { "id": "...", "url": "example.com", "monitoring_status": "setup" } }
}

Vérifier les signatures

Chaque requête porte un en-tête Palisade-Signature de la forme t=<unix seconds>,v1=<hex>. La signature est un HMAC-SHA256, calculé avec le secret de signature de votre point de terminaison, sur la chaîne <timestamp>.<raw request body>. Vérifiez la signature sur le corps brut — sérialiser de nouveau le JSON analysé change les octets, et la signature ne correspondra pas.

import crypto from 'node:crypto';

function verify(rawBody, header, secret, toleranceSeconds = 300) {
  if (!header) return false;

  const parts = Object.fromEntries(
    header.split(',').map((p) => p.split('=').map((s) => s.trim())),
  );

  const timestamp = Number(parts.t);
  if (!Number.isInteger(timestamp)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) return false;
  if (!parts.v1) return false;

  const expected = crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(parts.v1, 'hex');

  // timingSafeEqual throws on a length mismatch, and Buffer.from silently drops non-hex
  // characters, so both have to be checked before comparing.
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    if not header:
        return False

    try:
        parts = dict(p.strip().split("=", 1) for p in header.split(","))
        timestamp = int(parts["t"])
        received = parts["v1"]
    except (ValueError, KeyError):
        return False

    if abs(int(time.time()) - timestamp) > tolerance:
        return False

    signed = f"{timestamp}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received)

Le secret de signature est renvoyé une fois, à la création du point de terminaison, puis seulement si vous appelez POST /webhook-endpoints/{id}/rotate-secret. Le renouvellement prend effet immédiatement; les livraisons signées avec l’ancien secret échoueront donc à la vérification.

Garanties de livraison

Examinez ce qui s’est passé avec GET /webhook-endpoints/{id}/deliveries, qui renvoie les tentatives récentes avec leur code d’état de réponse et leur erreur. Les enregistrements de livraison sont conservés 30 jours, puis supprimés; traitez donc la liste comme une aide au débogage plutôt que comme des archives — si vous avez besoin d’un registre permanent des événements, conservez-les à leur réception. L’historique survit au point de terminaison : vous pouvez encore le lire après avoir supprimé le point de terminaison auquel il appartenait, ce qui est habituellement le moment où vous en avez besoin.

Délais à prévoir

Les événements reflètent un état validé; un domain.updated décrit donc toujours quelque chose qui est déjà vrai. Trois délais méritent d’être pris en compte dans votre conception.

En raison du premier point, abonnez-vous à task.reopened en plus de task.completed. Sinon, un billet fermé à l’achèvement n’apprendra jamais que le travail est revenu.

domain.score_changed se déclenche quand un domaine franchit une tranche plutôt qu’à chaque point. La tranche dans laquelle il est passé est score_band sur l’objet domaine, et celle qu’il a quittée se trouve dans data.previous_attributes; vous n’avez donc jamais à réimplémenter les seuils.