Documentation
1. Publier un événement
curl -X POST https://hookpulse.altixcode.com/api/v1/apps/APP_ID/events \
-H "Authorization: Bearer hp_…" \
-H "Content-Type: application/json" \
-d '{
"type": "invoice.paid",
"payload": { "id": "in_123", "amount": 4900 },
"dedupeKey": "invoice-123-paid"
}'La réponse est renvoyée dès que l'événement est enregistré et mis en file d'attente. Renseignez une dedupeKey pour éviter qu'une nouvelle tentative de votre exécuteur de tâches n'envoie l'événement en double.
2. Ce que votre client reçoit
POST /their/endpoint
Content-Type: application/json
HookPulse-Signature: t=1700000000,v1=<base64 hmac>
HookPulse-Delivery-Id: dlv_…
HookPulse-Timestamp: 1700000000
{"id":"evt_…","type":"invoice.paid","createdAt":"…","data":{…}}3. Vérification
Transmettez ceci à vos clients. Les deux erreurs courantes consistent à comparer avec === et à ignorer l'horodatage, et toutes deux échouent silencieusement.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(headers, rawBody, secret, toleranceSeconds = 300) {
const header = headers['hookpulse-signature']
const deliveryId = headers['hookpulse-delivery-id']
const timestamp = Number(/t=(\d+)/.exec(header)?.[1])
// Replay protection. Without this a captured request is valid forever.
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false
const expected = createHmac('sha256', secret)
.update(`${deliveryId}.${timestamp}.${rawBody}`)
.digest('base64')
return [...header.matchAll(/v1=([^,]+)/g)].some(([, presented]) => {
const a = Buffer.from(presented)
const b = Buffer.from(expected)
return a.length === b.length && timingSafeEqual(a, b)
})
}Vérifiez par rapport au corps brut, avant tout parsing JSON. La resérialisation modifie les octets et fait échouer chaque signature.
4. Rotation d'un secret
La rotation définit un nouveau secret tout en conservant l'ancien valide pendant la période de votre choix. Nous signons immédiatement avec le nouveau pour encourager la migration de vos clients, mais un destinataire vérifiant encore avec l'ancien secret continue de fonctionner jusqu'à l'expiration du délai.
5. Le journal de livraison
Générez un jeton de portail dans votre backend — au même format qu'une signature de webhook, en désignant l'un de vos clients — et appelez l'API du portail depuis votre propre page de paramètres. Chaque requête est limitée à l'abonné indiqué dans ce jeton, empêchant ainsi vos clients de voir les charges utiles des autres.
GET https://hookpulse.altixcode.com/api/portal/APP_ID/deliveries
POST https://hookpulse.altixcode.com/api/portal/APP_ID/deliveries/DELIVERY_ID/retry
Authorization: Bearer <portal token>Ou intégrez-le
Avec les forfaits Pro et Scale, intégrez directement <hookpulse-log> dans votre page de paramètres plutôt que de concevoir votre propre tableau. Même jeton, même portée — le journal s'affiche directement, dans un shadow root pour éviter tout conflit avec le CSS de votre page.
<script src="https://hookpulse.altixcode.com/embed/hookpulse-log.js"></script>
<hookpulse-log app-id="APP_ID" token="PORTAL_TOKEN" api-url="https://hookpulse.altixcode.com"></hookpulse-log>Limites transparentes
- La file d'attente est une table PostgreSQL. Ainsi, l'historique des livraisons et la file d'attente ne peuvent jamais être en désaccord, et un redémarrage ne perd rien. Ce n'est pas la conception la plus rapide possible, et à très haut débit, un courtier de messages ferait mieux.
- Le worker s'exécute au sein du processus web. La récupération est déjà sécurisée pour plusieurs workers, son extraction relève donc du déploiement plutôt que d'une réécriture — mais aujourd'hui, la charge des livraisons et celle des requêtes partagent le même processus.
- Nous ne suivons pas les redirections. En suivre une reviendrait à envoyer une charge utile signée vers un hôte que vous n'avez jamais indiqué. Tout code 3xx est traité comme un échec.
- Un endpoint qui échoue 20 fois de suite est désactivé. Une URL inaccessible ne doit pas continuer à épuiser les tentatives au détriment de toutes les autres.