Endpoints
`webhooks.list`, `get`, `create`, `update`, `delete`, `rotateSecret`, `test` et `listDeliveries`.
Toutes les méthodes
const endpoint = await openemail.webhooks.create({ url: 'https://acme.com/hooks/mail', eventTypes: ['email.sent', 'email.bounced'], description: 'Billing service',}) await store(endpoint.secret) await openemail.webhooks.list()await openemail.webhooks.get(endpoint.id)await openemail.webhooks.update(endpoint.id, { enabled: false })await openemail.webhooks.test(endpoint.id)const rotated = await openemail.webhooks.rotateSecret(endpoint.id)await openemail.webhooks.delete(endpoint.id)create est le SEUL moment où le secret est renvoyé, à part rotateSecret. Une lecture ne le répète jamais : stockez-le avant toute autre chose. Omettez eventTypes pour recevoir tous les événements, y compris ceux ajoutés plus tard.
rotateSecret n'a aucune fenêtre de recouvrement. L'ancien secret cesse de fonctionner immédiatement : déployez le nouveau avant de faire la rotation. L'appel n'est jamais réessayé automatiquement, car un réessai ferait une seconde rotation et invaliderait le secret que la première tentative a renvoyé.
Ce à quoi vous pouvez vous abonner
WEBHOOK_EVENTS est exporté pour que vous puissiez afficher la liste. Les événements sont ceux de la **boîte aux lettres**, pas de cette API : email.received se déclenche pour du courrier qui arrive dans l'application, et email.sent pour un message envoyé depuis le compositeur. S'abonner n'équivaut pas à observer votre propre trafic API.
Prouver que ça fonctionne
const result = await openemail.webhooks.test('whe_…')console.log(result.delivery?.status, result.delivery?.responseCode) const deliveries = await openemail.webhooks.listDeliveries('whe_…')for (const d of deliveries) console.log(d.eventType, d.status, d.responseCode, d.error)Un responseCode à null signifie qu'il n'y a eu aucune réponse (DNS, TLS, un dépassement de délai), ce qui n'est pas la même chose qu'une réponse valant 0. Chaque ligne porte attempt et maxAttempts : plusieurs lignes peuvent donc décrire un même événement ; le payload.id commun à ces lignes désigne l'événement, et le numéro de tentative désigne l'essai.
Paramètres : webhooks.create
urlstringobligatoire- L'adresse où les livraisons sont envoyées en POST. HTTPS uniquement, et l'hôte ne peut pas être `localhost`, un nom en `.localhost`/`.local`/`.internal`, ni une IP littérale de loopback, privée, CGNAT ou link-local. Il s'agit d'une requête côté serveur vers une adresse que vous fournissez : ces cas donnent donc un 422 sur `url` ; la vérification lit le nom d'hôte tel qu'il est écrit et ne résout jamais le DNS. Ce qui est stocké, c'est la sérialisation par l'analyseur d'URL de ce que vous avez envoyé : `https://acme.com` se relit donc `https://acme.com/`.
eventTypesWebhookEvent[]- Les événements qui atteignent cet endpoint : n'importe lequel des noms de `WEBHOOK_EVENTS`. `POST /webhooks` plafonne le tableau au nombre d'événements existants : un de plus donne un 422 sur `eventTypes` ; `PATCH` ne le plafonne pas. Seule la longueur est plafonnée, et un nom répété est stocké puis relu exactement tel que vous l'avez envoyé. Omis ou vide, il est stocké comme une liste vide, d'où sa relecture sous la forme `['*']`, et cela signifie tous les événements `email.*` sauf `email.replied`, soit quatorze aujourd'hui, et jamais les familles domaine ou suppression. Une famille ajoutée plus tard n'atteint jamais un endpoint qui ne l'a pas nommée : une intégration ne peut donc pas se mettre à recevoir, à la faveur d'une mise en production, une forme qu'elle n'a jamais vue.
descriptionstring- Un libellé pour l'endpoint, de 200 caractères au plus, pour qu'une liste de webhooks se lise comme des noms plutôt que comme une colonne d'URL. S'il est omis, il est stocké et renvoyé comme null.
Réponse : CreatedWebhookResource
object'webhook'- Toujours `'webhook'`, le même discriminant que renvoie une simple lecture, car le secret n'est qu'une clé supplémentaire sur la forme ordinaire plutôt qu'un type d'objet à part. La présence de `secret` dépend de la méthode que vous avez appelée, pas de ce champ.
idstring- L'identifiant de l'endpoint : `whe_` suivi de 24 caractères hexadécimaux. Tous les autres appels de webhook le prennent : `get`, `update`, `delete`, `rotateSecret`, `test` et `listDeliveries`.
urlstring- L'endpoint tel qu'il est stocké, une fois passés les contrôles HTTPS et d'hôtes interdits. C'est l'URL analysée puis re-sérialisée : comparez donc à cette valeur plutôt qu'à la chaîne que vous avez envoyée.
descriptionstring | null- Le libellé que vous lui avez donné, ou null si vous n'en avez pas donné. Un `update` qui envoie un null explicite le remet à null.
eventTypesWebhookEvent[] | ['*']- Les événements souscrits, ou `['*']` lorsque l'endpoint n'en a nommé aucun. `['*']` est la façon dont une liste stockée vide est rendue à la lecture et ne peut pas être renvoyée ; cette valeur représente les treize événements de message et non le catalogue entier. `create` et `update` n'acceptent que les noms d'événements littéraux.
enabledboolean- Indique si les livraisons sont tentées ; un endpoint désactivé est ignoré lors de la distribution des événements et conserve son secret et son historique de livraisons. Toujours true ici, puisque `WebhookCreate` n'a pas d'`enabled` et que seul `WebhookPatch` en a un.
lastDeliveryAtstring | null- Horodatage ISO 8601 de la dernière TENTATIVE de livraison, et non de la dernière réussite. Il est aussi posé après un POST en échec : il vous dit que l'endpoint a été sollicité, et `listDeliveries` vous dit comment cela s'est passé. Null jusqu'à la première tentative, donc toujours null sur `create`.
createdAtstring- Horodatage ISO 8601 de l'enregistrement de l'endpoint. `list` renvoie les endpoints du plus récent au plus ancien selon ce champ.
secretstring- La clé HMAC-SHA-256 qui signe le `X-OpenEmail-Signature` de chaque livraison : `whsec_` suivi de 32 octets aléatoires en base64url, et c'est elle que vous passez à `verifyWebhookSignature`. Renvoyée par `create` et `rotateSecret`, et par rien d'autre. Une lecture ne la répète jamais : stockez-la maintenant ; un secret perdu ne peut être remplacé que par `rotateSecret`, qui invalide l'ancien immédiatement.