Webhooks pour les développeurs
Disponible avecFreeProResearch
Un webhook fait envoyer à Readsira une requête HTTP POST à votre serveur quand quelque chose se
produit dans votre compte, par exemple quand vous enregistrez un marque-page. Cette page s'adresse
au code qui le reçoit : les événements, la charge utile, la signature et les règles de relance. Pour
ajouter un webhook dans l'application, voir Webhooks.
Avant de commencer
- Une adresse
httpouhttpspublique qui acceptePOST. Readsira refuselocalhostet les adresses de réseau privé, et ne suit pas les redirections. - Votre serveur doit lire le corps brut de la requête. Il vous faut les octets exacts pour vérifier la signature.
À quoi ressemble une livraison
Chaque livraison est un POST avec un corps JSON et trois en-têtes :
| En-tête | Valeur |
|---|---|
content-type | application/json |
x-readsira-event | Le nom de l'événement, par exemple bookmark.created |
x-readsira-signature | sha256= suivi du HMAC du corps, en hexadécimal |
Le corps est une enveloppe :
{
"event": "bookmark.created",
"timestamp": "2026-11-04T09:15:02.481Z",
"data": {
"bookmark_id": "c71d0e3a-5b1c-4c58-9d6f-0f6c0d2a9e11",
"user_id": "7b0e1f42-2a43-4f0b-8f0e-1d2c3b4a5f60",
"url": "https://example.com/grid-lab",
"title": "Grid Lab: long-duration storage",
"tags": ["energy"],
"collection_id": "a2c4e6f8-1b3d-4f5a-8c7e-9d0b1a2c3d4e"
}
}
timestamp est le moment où Readsira a construit la livraison. Les valeurs ci-dessus sont un
exemple.
Événements
Vous choisissez les événements de chaque webhook. Un nouveau webhook reçoit bookmark.created.
| Événement | Envoyé quand | data |
|---|---|---|
bookmark.created | Vous enregistrez un marque-page | bookmark_id, user_id, url, title, tags, collection_id |
bookmark.updated | Vous modifiez un marque-page | bookmarkId, userId, bookmark, changes, notesChanged |
bookmark.deleted | Un marque-page est supprimé | bookmarkId, userId, bookmark |
bookmark.visited | Vous ouvrez un marque-page | bookmarkId, userId, visitedAt |
output.created | Une production de projet se termine | id, projectId, kind, title, version, sourceCount, completedAt |
L'application liste aussi collection.created, collection.updated et collection.deleted.
Readsira ne les envoie pas encore : un webhook qui n'écoute que ceux-là ne reçoit rien.
Dans bookmark.updated et bookmark.deleted, bookmark est un instantané avec id, url,
title, description, collectionId, tags, isStarred, isPublic, domain, notes,
triageStatus et captureIntent. changes liste les noms des champs définis par la requête. Pour
output.created, la charge utile ne contient que des identifiants et des métadonnées, jamais le
texte de la production ; lisez-le dans l'application.
Le bouton Tester de l'application envoie un événement de plus, webhook.test, avec
data: { "message": "Test webhook from Readsira", "userId": "..." }. Il est signé comme les autres.
Les webhooks sont suspendus tant que la suppression de votre compte est programmée.
Vérifier la signature
La signature prouve que la requête vient de Readsira et que personne n'a modifié le corps. Readsira
calcule un HMAC-SHA256 sur le corps brut, avec le secret du webhook comme clé, et envoie le
résultat sous la forme sha256= suivi de 64 caractères hexadécimaux.
Le secret est la chaîne hexadécimale de 64 caractères que l'application affiche quand vous créez le webhook ou renouvelez son secret. Utilisez cette chaîne telle quelle, comme du texte. Ne la décodez pas depuis l'hexadécimal. Elle n'est affichée qu'une seule fois.
Un récepteur en Node, sans aucun paquet :
import { createHmac, timingSafeEqual } from 'node:crypto';
import { createServer } from 'node:http';
const SECRET = process.env.READSIRA_WEBHOOK_SECRET;
function isFromReadsira(rawBody, signatureHeader) {
const expected = 'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}
createServer((req, res) => {
const chunks = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => {
const rawBody = Buffer.concat(chunks);
if (!isFromReadsira(rawBody, req.headers['x-readsira-signature'])) {
res.writeHead(401).end();
return;
}
const { event, timestamp, data } = JSON.parse(rawBody.toString('utf8'));
console.log(event, timestamp, data);
res.writeHead(204).end();
});
}).listen(3000);
Trois détails causent la plupart des échecs :
- Hachez les octets bruts. Si votre framework analyse d'abord le JSON et que vous le sérialisez de
nouveau, les octets diffèrent et la vérification échoue. Avec Express, utilisez
express.raw({ type: 'application/json' })pour la route du webhook. - Comparez en temps constant, comme le fait
timingSafeEqual, et non avec===. - Après avoir renouvelé le secret dans l'application, les requêtes signées avec l'ancien secret
échouent à la vérification. Mettez d'abord à jour
READSIRA_WEBHOOK_SECRET.
Pour rejeter les rejeux, comparez timestamp à l'horloge et écartez les livraisons plus anciennes
que quelques minutes. L'horodatage se trouve dans le corps signé : il ne peut donc pas être modifié
sans casser la signature. Une relance (ci-dessous) transporte de nouveau l'événement d'origine ;
rendez donc votre gestionnaire sûr à exécuter deux fois.
Répondre rapidement
Répondez avec n'importe quel statut 2xx dès que vous avez stocké l'événement. Readsira attend
5 secondes et lit au plus 64 Ko de votre réponse.
| Votre réponse | Ce que fait Readsira |
|---|---|
2xx | La livraison est comptée comme livrée. |
5xx ou aucune réponse en 5 secondes | La livraison est retentée, jusqu'à 4 tentatives en tout, avec une attente entre elles qui commence à 30 secondes et double à chaque fois. |
4xx | La livraison est comptée comme échouée et n'est pas retentée. |
L'application montre, pour chaque webhook, combien de fois il s'est déclenché et quand pour la dernière fois.
Conseils
- Gardez le gestionnaire court. Mettez le travail dans une file et répondez d'abord.
- Utilisez le bouton Tester pendant le développement, avant d'enregistrer un vrai marque-page.
- Stockez le secret comme un mot de passe. Renouvelez-le s'il fuit.
Voir aussi
- Webhooks (configuration dans l'application)
- Exemples
- Limites de débit et erreurs