En bref : Le sondage (polling) vérifie les mises à jour périodiquement (simple mais inefficace). Les webhooks poussent les mises à jour en temps réel (efficace mais complexe). Utilisez le sondage pour les vérifications peu fréquentes, les webhooks pour les mises à jour en temps réel. L'API Petstore moderne prend en charge les deux modèles avec une livraison fiable des webhooks.
Essayez Apidog dès aujourd'hui
Comprendre la différence
Sondage (Polling) : Le client demande « Y a-t-il des mises à jour ? » de manière répétée.
Webhooks : Le serveur dit « Voici une mise à jour ! » quand quelque chose se produit.
- Sondage = Vérifier votre boîte aux lettres toutes les heures
- Webhooks = Le facteur sonne à la porte quand le courrier arrive
Sondage (Polling) : Comment ça fonctionne
Le client effectue des requêtes périodiques pour vérifier les changements.
// Sonde toutes les 30 secondes
setInterval(async () => {
const response = await fetch('https://petstoreapi.com/api/v1/orders/123');
const order = await response.json();
if (order.status === 'completed') {
console.log('Order completed!', order);
clearInterval(pollInterval);
}
}, 30000);
Modèles de sondage :
Sondage simple :
GET /api/v1/orders/123
# Retourne l'état actuel de la commande
Sondage conditionnel (ETag) :
GET /api/v1/orders/123
If-None-Match: "abc123"
# Retourne 304 Not Modified si inchangé
# Retourne 200 avec de nouvelles données si modifié
Sondage basé sur une date (since-based) :
GET /api/v1/orders/123/events?since=1710331200
# Retourne les événements depuis l'horodatage
Webhooks : Comment ça fonctionne
Le serveur envoie une requête HTTP POST à votre endpoint lorsque des événements se produisent.
Flux de configuration :
// 1. Enregistrer l'endpoint du webhook
POST /api/v1/webhooks
{
"url": "https://myapp.com/webhooks/petstore",
"events": ["order.created", "order.completed"],
"secret": "whsec_abc123"
}
// 2. Le serveur envoie le webhook lorsqu'un événement se produit
POST https://myapp.com/webhooks/petstore
{
"id": "evt_123",
"type": "order.completed",
"created": 1710331200,
"data": {
"orderId": "123",
"status": "completed",
"completedAt": "2024-01-01T12:00:00Z"
}
}
// 3. Vérifier et traiter le webhook
// Répondre avec 200 OK
Quand utiliser le sondage (polling)
Bon pour :
- Vérifications peu fréquentes (une fois par heure)
- Petit nombre de ressources
- Implémentations simples
- Lorsque vous contrôlez le client
- Tests et débogage
Exemples :
- Vérifier le statut d'un rapport quotidien
- Synchroniser les contacts toutes les quelques minutes
- Surveiller l'état du serveur
- Vérifier le statut de paiement (peu fréquent)
Le sondage est approprié lorsque :
- Les mises à jour sont rares
- Un léger délai est acceptable
- Vous souhaitez une implémentation simple
- La ressource est petite
Quand utiliser les webhooks
Bon pour :
- Mises à jour en temps réel
- Nombreuses ressources à surveiller
- Événements sensibles au temps
- Intégrations tierces
- Mises à jour à haute fréquence
Exemples :
- Confirmations de paiement
- Messages de chat
- Alertes de prix boursiers
- Changements de statut de commande
- Notifications CI/CD
Les webhooks sont meilleurs lorsque :
- Les mises à jour doivent être immédiates
- Le sondage serait inefficace
- De nombreux clients surveillent la même ressource
- Vous souhaitez réduire la charge du serveur
Tableau comparatif
| Facteur | Sondage (Polling) | Webhooks |
|---|---|---|
| Latence | Jusqu'à l'intervalle de sondage | Temps réel |
| Charge serveur | Élevée (nombreuses requêtes vides) | Faible (seulement les événements réels) |
| Complexité | Simple | Complexe |
| Fiabilité | Élevée (le client contrôle la nouvelle tentative) | Moyenne (nécessite une logique de nouvelle tentative) |
| Configuration | Aucune | Enregistrement de l'endpoint |
| Problèmes de pare-feu | Aucun (sortant uniquement) | Peut nécessiter une liste blanche |
| Coût | Plus élevé (plus de requêtes) | Moins élevé (moins de requêtes) |
| Idéal pour | Vérifications peu fréquentes | Mises à jour en temps réel |
Implémenter le sondage (polling)
Sondage de base
async function pollOrderStatus(orderId, callback) {
let lastStatus = null;
const poll = async () => {
try {
const response = await fetch(`https://petstoreapi.com/api/v1/orders/${orderId}`);
const order = await response.json();
// Rappeler seulement si le statut a changé
if (order.status !== lastStatus) {
lastStatus = order.status;
callback(order);
}
// Arrêter le sondage si l'état est terminal
if (['completed', 'cancelled'].includes(order.status)) {
return;
}
// Continuer le sondage
setTimeout(poll, 5000);
} catch (error) {
console.error('Polling error:', error);
setTimeout(poll, 30000); // Recommencer après une erreur
}
};
poll();
}
// Utilisation
pollOrderStatus('order-123', (order) => {
console.log(`Order status: ${order.status}`);
});
Sondage intelligent (retrait exponentiel)
async function smartPoll(url, callback, options = {}) {
const {
maxRetries = 10,
initialInterval = 1000,
maxInterval = 60000,
stopCondition = () => false
} = options;
let retries = 0;
let interval = initialInterval;
let lastData = null;
const poll = async () => {
try {
const response = await fetch(url);
const data = await response.json();
// Rappeler si les données ont changé
if (JSON.stringify(data) !== JSON.stringify(lastData)) {
lastData = data;
callback(data);
}
// Arrêter si la condition est remplie
if (stopCondition(data)) {
return;
}
// Réinitialiser l'intervalle en cas de requête réussie
interval = initialInterval;
} catch (error) {
retries++;
if (retries >= maxRetries) {
throw new Error('Max retries exceeded');
}
}
// Planifier le prochain sondage avec un retrait exponentiel
setTimeout(poll, interval);
interval = Math.min(interval * 2, maxInterval);
};
poll();
}
// Utilisation : Sonde la commande jusqu'à ce qu'elle soit complétée
smartPoll('https://petstoreapi.com/api/v1/orders/123',
(order) => console.log('Order:', order),
{
stopCondition: (order) => ['completed', 'cancelled'].includes(order.status),
initialInterval: 2000,
maxInterval: 30000
}
);
Sondage avec ETag
async function pollWithEtag(url, callback) {
let etag = null;
const poll = async () => {
const headers = {};
if (etag) {
headers['If-None-Match'] = etag;
}
const response = await fetch(url, { headers });
if (response.status === 304) {
// Non modifié, continuer le sondage
setTimeout(poll, 30000);
return;
}
const data = await response.json();
etag = response.headers.get('etag');
callback(data);
setTimeout(poll, 30000);
};
poll();
}
Implémenter les webhooks
Enregistrer les webhooks
// Enregistrer l'endpoint du webhook
async function registerWebhook(url, events) {
const response = await fetch('https://petstoreapi.com/api/v1/webhooks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
url,
events,
secret: generateSecret()
})
});
return response.json();
}
function generateSecret() {
return 'whsec_' + crypto.randomBytes(32).toString('hex');
}
Recevoir les webhooks
const express = require('express');
const crypto = require('crypto');
const app = express();
// Parseur de corps brut pour la vérification de signature
app.use('/webhooks', express.raw({ type: 'application/json' }));
app.post('/webhooks/petstore', async (req, res) => {
const signature = req.headers['x-petstore-signature'];
const body = req.body;
// Vérifier la signature
const isValid = verifySignature(body, signature, process.env.WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(body.toString());
// Traiter l'événement
switch (event.type) {
case 'order.created':
await handleOrderCreated(event.data);
break;
case 'order.completed':
await handleOrderCompleted(event.data);
break;
case 'order.cancelled':
await handleOrderCancelled(event.data);
break;
}
// Accuser réception
res.status(200).json({ received: true });
});
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Tester les webhooks localement
# Utiliser ngrok pour exposer l'endpoint local
ngrok http 3000
# Enregistrer l'URL ngrok comme endpoint de webhook
curl -X POST https://petstoreapi.com/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-d '{
"url": "https://abc123.ngrok.io/webhooks/petstore",
"events": ["order.created", "order.completed"]
}'
Livraison fiable des webhooks
Les webhooks peuvent échouer. Implémentez une logique de nouvelle tentative.
Côté expéditeur (serveur)
// Mettre en file d'attente les webhooks pour la livraison
const webhookQueue = [];
async function sendWebhook(event) {
const webhooks = await db.webhooks.findMany({
where: { events: { contains: event.type } }
});
for (const webhook of webhooks) {
webhookQueue.push({
webhook,
event,
attempts: 0,
nextAttempt: Date.now()
});
}
processQueue();
}
async function processQueue() {
const now = Date.now();
for (const item of webhookQueue) {
if (item.nextAttempt > now) continue;
try {
await deliverWebhook(item);
// Supprimer de la file d'attente en cas de succès
webhookQueue.splice(webhookQueue.indexOf(item), 1);
} catch (error) {
// Planifier une nouvelle tentative avec un retrait exponentiel
item.attempts++;
item.nextAttempt = now + getBackoff(item.attempts);
if (item.attempts >= 5) {
// Marquer comme échoué après 5 tentatives
await markWebhookFailed(item);
webhookQueue.splice(webhookQueue.indexOf(item), 1);
}
}
}
setTimeout(processQueue, 5000);
}
function getBackoff(attempt) {
// 1min, 5min, 15min, 1h, 4h
const delays = [60000, 300000, 900000, 3600000, 14400000];
return delays[attempt - 1] || delays[delays.length - 1];
}
async function deliverWebhook({ webhook, event }) {
const signature = generateSignature(event, webhook.secret);
const response = await fetch(webhook.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Petstore-Signature': signature,
'X-Petstore-Event': event.type
},
body: JSON.stringify(event),
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
}
Côté récepteur (client)
// Gestion idempotente des webhooks
const processedEvents = new Set();
app.post('/webhooks/petstore', async (req, res) => {
const event = JSON.parse(req.body.toString());
// Ignorer si déjà traité (idempotence)
if (processedEvents.has(event.id)) {
return res.status(200).json({ received: true });
}
try {
await processEvent(event);
processedEvents.add(event.id);
// Nettoyer les anciens ID d'événements (garder les 1000 derniers)
if (processedEvents.size > 1000) {
const arr = Array.from(processedEvents);
arr.slice(0, arr.length - 1000).forEach(id => processedEvents.delete(id));
}
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
// Retourner 5xx pour déclencher une nouvelle tentative
res.status(500).json({ error: 'Processing failed' });
}
});
async function processEvent(event) {
// Traiter l'événement
switch (event.type) {
case 'order.created':
await handleOrderCreated(event.data);
break;
// ... gérer les autres événements
}
}
Approche hybride
Utilisez à la fois le sondage et les webhooks pour les mises à jour critiques.
class OrderMonitor {
constructor(orderId, callback) {
this.orderId = orderId;
this.callback = callback;
this.pollInterval = null;
}
async start() {
// Commencer par le sondage pour un feedback immédiat
this.startPolling();
// Enregistrer le webhook pour une mise à jour en temps réel
await this.registerWebhook();
}
startPolling() {
this.pollInterval = setInterval(async () => {
const order = await this.fetchOrder();
this.callback(order);
if (['completed', 'cancelled'].includes(order.status)) {
this.stop();
}
}, 10000);
}
async registerWebhook() {
const response = await fetch('https://petstoreapi.com/api/v1/webhooks', {
method: 'POST',
headers: { 'Authorization': `Bearer ${TOKEN}` },
body: JSON.stringify({
url: 'https://myapp.com/webhooks/petstore',
events: [`order.${this.orderId}`],
oneTime: true // Suppression automatique après la première livraison
})
});
this.webhookId = (await response.json()).id;
}
stop() {
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
if (this.webhookId) {
fetch(`https://petstoreapi.com/api/v1/webhooks/${this.webhookId}`, {
method: 'DELETE'
});
}
}
}
FAQ
Q : À quelle fréquence dois-je sonder ? Dépend de l'urgence. 30 secondes pour du quasi temps réel. 5 minutes pour les moins urgents. Équilibrez la fraîcheur des données et la charge du serveur.
Q : Que se passe-t-il si mon endpoint de webhook est indisponible ? Les bons fournisseurs de webhooks réessayent avec un retrait exponentiel. Implémentez l'idempotence pour gérer les livraisons en double.
Q : Comment sécuriser les webhooks ? Vérifiez les signatures à l'aide de secrets partagés. Utilisez HTTPS uniquement. Validez les données d'événement.
Q : Puis-je utiliser les webhooks pour les données historiques ? Non. Les webhooks sont uniquement pour les nouveaux événements. Utilisez le sondage ou les API de traitement par lots pour les données historiques.
Q : Dois-je utiliser le sondage ou les webhooks pour les applications mobiles ? Le sondage est plus simple pour le mobile. Les webhooks nécessitent des notifications push comme intermédiaire.
Q : Comment déboguer les problèmes de webhook ? Utilisez des outils comme webhook.site pour les tests. Enregistrez toutes les livraisons de webhooks. Fournissez un historique des événements de webhook dans votre API.
L'API Petstore moderne prend en charge le sondage et les webhooks. Consultez le guide des webhooks pour les détails d'implémentation. Testez les intégrations de webhooks avec Apidog.
Top comments (0)