Automatiser les alertes de prix de l'or et de l'argent avec des webhooks
Automatisez vos alertes de prix de l'or et de l'argent par webhooks signés : conditions, délai de carence, payload, vérification HMAC-SHA256 en Node et Python.
Des alertes plutôt que du polling
Beaucoup d'intégrations ne s'intéressent aux prix qu'à des moments précis : quand l'or franchit un niveau surveillé par un client, quand l'argent chute brutalement, ou quand une devise bouge assez pour justifier de revoir les prix d'un catalogue. Interroger /latest une fois par minute pour saisir ces moments coûte environ 45 000 requêtes par mois, et il vous reste encore à écrire la logique qui détecte le franchissement.
Les alertes de prix de MetalAPI déplacent ce travail côté serveur. Le moteur d'alertes compare chaque alerte active aux prix en direct dès leur arrivée et vous prévient lorsqu'une condition est remplie. Les vérifications d'alertes ne consomment pas votre quota d'API, et votre code ne s'exécute que lorsqu'il se passe quelque chose.
| Plan | Alertes | Livraison |
|---|---|---|
| Free | Aucune | — |
| Lite | 3 | |
| Standard | 10 | |
| Advanced | 25 | E-mail ou webhook |
| Pro | 50 | E-mail ou webhook |
| Ultra | 100 | E-mail ou webhook |
| Business | 200 | E-mail ou webhook |
| Enterprise | 500 | E-mail ou webhook |
Vous gérez les alertes sous Alerts dans le tableau de bord. Le guide des webhooks sert de référence rapide ; cet article couvre les détails nécessaires pour un endpoint en production. Si vous avez besoin de chaque mise à jour de prix plutôt que d'événements de seuil, l'article sur le streaming en temps réel présente l'autre approche.
Conditions d'alerte et déclenchement
Chaque alerte surveille un symbole exprimé dans une devise, par exemple XAU en USD ou XAG en EUR. Tout symbole actif convient, y compris le platine, le palladium, les métaux industriels, l'énergie, les devises et les cryptos. Il existe quatre conditions :
| Condition | Se déclenche lorsque | Seuil |
|---|---|---|
above | prix ≥ seuil | Un prix dans la devise de l'alerte |
below | prix ≤ seuil | Un prix dans la devise de l'alerte |
change_pct_up | variation depuis la clôture précédente ≥ seuil | Un pourcentage inférieur à 100 |
change_pct_down | variation depuis la clôture précédente ≤ −seuil | Un pourcentage inférieur à 100, saisi sous forme de nombre positif |
Les seuils des métaux s'entendent à l'once troy, l'unité de référence. Cela vaut aussi pour les métaux industriels comme le cuivre, souvent cotés à la livre ailleurs. L'énergie utilise l'unité de son contrat, le baril pour le pétrole brut, et le payload du webhook indique toujours l'unité.
Les prix et les variations en pourcentage sont calculés dans la devise de l'alerte. Pour XAG en EUR, une hausse de l'euro peut à elle seule faire passer l'argent sous votre seuil ou déclencher change_pct_down, même si l'argent est inchangé en dollars américains.
Les alertes se déclenchent au franchissement, et non à chaque mise à jour de prix tant que la condition reste vraie. Lorsque l'or dépasse 4 000 $, vous recevez une seule notification. L'alerte se réarme une fois que la condition est redevenue fausse : un repli ultérieur sous ce niveau, suivi d'un nouveau franchissement à la hausse, vous notifie donc à nouveau.
Le délai de carence est le temps minimal entre deux notifications d'une même alerte : de 5 minutes à 7 jours (10 080 minutes), 60 minutes par défaut. Un prix qui oscille autour d'un seuil peut le franchir de nombreuses fois en une heure, et le délai de carence transforme tout cela en un seul message. Pour un niveau qui ne compte qu'une fois, choisissez un long délai de carence et mettez l'alerte en pause après son déclenchement.
Un comportement s'avère pratique pour les tests. Une nouvelle alerte n'a pas d'état antérieur : si sa condition est déjà vraie, elle se déclenche à la prochaine mise à jour de prix, généralement en moins d'une minute pendant les heures de marché. Une alerte sur XAU au-dessus de 1 USD avec un délai de carence de 5 minutes vous donne une vraie livraison sur laquelle tester. Supprimez-la ensuite.
Livraison par e-mail ou par webhook
Les alertes par e-mail sont disponibles à partir du plan Lite. Elles s'adressent à des personnes : un objet comme « MetalAPI alert: Gold (XAU) rose above 4000 USD », le prix actuel et la note que vous avez associée à l'alerte.
Les webhooks, disponibles à partir du plan Advanced, s'adressent à des systèmes : un POST HTTPS avec un corps JSON vers l'URL de votre choix, signé pour que vous puissiez prouver qu'il provient de MetalAPI. Une URL de webhook doit :
- utiliser
https://, - pointer vers une adresse IP publique (les plages privées, de bouclage et link-local sont rejetées à l'enregistrement de l'alerte, puis vérifiées à nouveau au moment de la livraison),
- ne contenir ni nom d'utilisateur ni mot de passe.
Les redirections ne sont pas suivies : saisissez donc l'URL finale. Si un compte passe à un plan sans webhooks, ses alertes webhook sont livrées par e-mail au lieu d'être abandonnées.
Tous les webhooks d'un compte sont signés avec un seul secret qui commence par whsec_. Vous pouvez le créer, l'afficher, le copier et le renouveler dans le panneau Webhook signing secret de la page Alerts. Si vous n'en avez pas créé, il est généré lors de la première livraison de webhook, puis affiché dans le panneau. Le renouvellement prend effet immédiatement, ce qui a son importance pour le code de vérification ci-dessous.
La requête du webhook
Chaque livraison ressemble à ceci :
POST /webhooks/metalapi HTTP/1.1
Content-Type: application/json
User-Agent: MetalAPI-Webhooks/1.0
X-MetalAPI-Timestamp: 1792765800
X-MetalAPI-Signature: t=1792765800,v1=5f0c1e…9a2b
{
"event": "alert.triggered",
"id": 18342,
"created": 1792765800,
"alert": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"symbol": "XAU",
"base": "USD",
"condition": "above",
"threshold": 4000,
"note": "Gold above 4k"
},
"price": 4001.25,
"unit": "troy_oz"
}| Champ | Signification |
|---|---|
event | Actuellement toujours alert.triggered. Ignorez les valeurs inconnues pour que de nouveaux types d'événements ne cassent jamais votre handler. |
id | Identifiant numérique de l'événement, unique pour chaque alerte déclenchée et identique d'une relance à l'autre |
created | Heure Unix à laquelle l'alerte s'est déclenchée |
alert | L'alerte telle que configurée : id (un UUID), symbol, base, condition, threshold et note |
price | Le prix qui a déclenché l'alerte, en base par unit |
unit | troy_oz pour les métaux, bbl, mmbtu ou gal pour l'énergie, currency pour les devises, coin pour les cryptos |
Le champ note, jusqu'à 200 caractères, est un endroit pratique pour les informations de routage que vous définissez dans le tableau de bord, comme un nom d'équipe, un canal ou une référence produit (SKU).
Vérifier la signature en Node.js
L'en-tête X-MetalAPI-Signature a la forme t=<timestamp>,v1=<signature>. La signature est le HMAC-SHA256, en hexadécimal minuscule, de la chaîne <timestamp>.<raw body>, calculé avec votre secret whsec_ comme clé. L'horodatage est aussi envoyé seul dans X-MetalAPI-Timestamp. Trois détails déterminent si une vérification fonctionne en production :
- Hachez les octets bruts. Analyser le JSON puis le resérialiser peut modifier les espaces ou le format des nombres et casser la signature. Lisez le corps sous forme de buffer.
- Vérifiez l'horodatage de l'en-tête. Utilisez
tde l'en-tête, et noncreateddu corps. Chaque relance est signée à nouveau au moment de l'envoi avec un nouveaut, tandis quecreatedconserve l'heure d'origine. Une fenêtre anti-rejeu fondée surcreatedrejetterait des relances valides. - Acceptez deux secrets pendant un renouvellement. Le renouvellement dans le tableau de bord bascule immédiatement la signature sur le nouveau secret. Ajoutez le nouveau secret à côté de l'ancien et déployez ; les livraisons qui échouent entre-temps sont relancées pendant quelques minutes. Retirez l'ancien secret une fois que le nouveau fonctionne.
Avec Express, express.raw() fournit le corps sous forme de Buffer :
// webhook-server.mjs — Node.js 18+, npm install express
import crypto from "node:crypto";
import express from "express";
// Séparés par des virgules : le secret actuel, plus le précédent pendant un renouvellement.
const SECRETS = (process.env.METALAPI_WEBHOOK_SECRETS ?? "").split(",").filter(Boolean);
const TOLERANCE_S = 300; // rejeter les signatures de plus de 5 minutes
export function verifySignature(rawBody, header) {
const parts = {};
for (const item of (header ?? "").split(",")) {
const i = item.indexOf("=");
if (i > 0) parts[item.slice(0, i).trim()] = item.slice(i + 1).trim();
}
const ts = parts.t ?? "";
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > TOLERANCE_S) return false;
if (!/^[0-9a-f]{64}$/.test(parts.v1 ?? "")) return false;
const given = Buffer.from(parts.v1, "hex");
return SECRETS.some((secret) => {
const expected = crypto.createHmac("sha256", secret).update(`${ts}.`).update(rawBody).digest();
return crypto.timingSafeEqual(expected, given);
});
}
async function handleAlert(event) {
const { alert, price, unit } = event;
console.log(`${alert.symbol}/${alert.base} ${alert.condition} ${alert.threshold}: ${price} per ${unit}`);
}
const app = express();
app.post("/webhooks/metalapi", express.raw({ type: "application/json", limit: "64kb" }), (req, res) => {
if (!Buffer.isBuffer(req.body) || !verifySignature(req.body, req.get("X-MetalAPI-Signature"))) {
return res.status(401).send("invalid signature");
}
const event = JSON.parse(req.body.toString("utf8"));
res.sendStatus(204); // répondre en moins de 8 secondes, puis faire le travail
if (event.event === "alert.triggered") {
handleAlert(event).catch((err) => console.error("alert handler failed:", err));
}
});
app.listen(3000);Le handler répond avant d'effectuer le moindre travail. Si ce travail est important, stockez d'abord l'événement de façon durable (les sections suivantes montrent comment), afin qu'un plantage après la réponse ne puisse pas le perdre.
Vérifier la signature en Python
La même vérification en Python n'utilise que la bibliothèque standard. hmac.compare_digest assure la comparaison en temps constant :
# webhook_app.py — pip install flask, puis : flask --app webhook_app run --port 3000
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
# Séparés par des virgules : le secret actuel, plus le précédent pendant un renouvellement.
SECRETS = [s for s in os.environ.get("METALAPI_WEBHOOK_SECRETS", "").split(",") if s]
TOLERANCE_S = 300 # rejeter les signatures de plus de 5 minutes
app = Flask(__name__)
def verify_signature(raw: bytes, header: str) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
ts, given = parts.get("t", ""), parts.get("v1", "")
if not ts.isdigit() or abs(time.time() - int(ts)) > TOLERANCE_S:
return False
signed = ts.encode() + b"." + raw
return any(
hmac.compare_digest(hmac.new(s.encode(), signed, hashlib.sha256).hexdigest(), given)
for s in SECRETS
)
@app.post("/webhooks/metalapi")
def metalapi_webhook():
raw = request.get_data() # octets bruts, avant toute analyse JSON
if not verify_signature(raw, request.headers.get("X-MetalAPI-Signature", "")):
abort(401)
event = json.loads(raw)
if event.get("event") == "alert.triggered":
alert = event["alert"]
# Confiez ici l'événement à une file de tâches et répondez rapidement.
app.logger.warning("%s/%s %s %s: %s", alert["symbol"], alert["base"],
alert["condition"], alert["threshold"], event["price"])
return "", 204Les autres frameworks ne diffèrent que par la façon de lire le corps brut : await request.body() dans FastAPI et request.body dans Django. Quel que soit le framework, assurez-vous qu'aucun middleware n'a analysé et consommé le corps avant l'exécution de votre handler.
Relances, doublons et temps de réponse
Une livraison réussit lorsque votre endpoint répond avec un statut 2xx quelconque en moins de 8 secondes. Les délais dépassés, les erreurs de connexion, les redirections et tous les autres statuts comptent comme des échecs et donnent lieu à des relances :
| Tentative | Moment |
|---|---|
| 1 | Dès le déclenchement de l'alerte |
| 2 | Environ 1 minute après le déclenchement de l'alerte |
| 3 | Environ 4 minutes après le déclenchement de l'alerte |
Après trois tentatives, l'événement est marqué comme échoué. Le tableau Recent deliveries de la page Alerts liste chaque événement avec son résultat, y compris le statut HTTP ou le message d'erreur de la dernière tentative. C'est le premier endroit à consulter lorsqu'un endpoint se comporte mal.
Les relances réutilisent le même id d'événement. Une livraison que votre serveur a traitée mais à laquelle il a répondu au-delà de 8 secondes compte comme un échec du côté de MetalAPI et arrive de nouveau : le handler doit donc être idempotent. Le plus simple est une table indexée par l'identifiant de l'événement :
create table metalapi_events (
event_id bigint primary key, -- "id" du payload, identique d'une relance à l'autre
alert_id uuid not null,
payload jsonb not null,
received_at timestamptz not null default now(),
processed_at timestamptz
);
-- Dans le handler du webhook, après la vérification de la signature et avant de répondre :
insert into metalapi_events (event_id, alert_id, payload)
values ($1, $2, $3)
on conflict (event_id) do nothing;Insérez l'événement, répondez 204, et laissez un worker traiter les lignes dont processed_at est nul. Une livraison en double se heurte à la clé primaire et ne change rien. L'insertion prend quelques millisecondes, ce qui vous laisse largement dans la fenêtre de 8 secondes, même lorsque le travail qui suit est lent.
Transférer les alertes vers une messagerie et d'autres systèmes
Un usage courant consiste à publier les alertes dans la messagerie d'une équipe. Slack et Discord acceptent tous deux les webhooks entrants, mais n'y faites pas pointer directement une alerte MetalAPI : ils attendent un autre payload, et vous perdriez la vérification de signature. Vérifiez sur votre propre endpoint, puis mettez en forme et transférez. Cette version de handleAlert remplace la fonction de journalisation provisoire du serveur Node.js ci-dessus :
const VERBS = { above: "rose above", below: "fell below", change_pct_up: "is up", change_pct_down: "is down" };
async function handleAlert({ alert, price, unit }) {
const level = alert.condition.startsWith("change_pct")
? `${alert.threshold}% since the previous close`
: `${alert.threshold} ${alert.base}`;
const per = unit && unit !== "currency" ? ` per ${unit}` : "";
const text = `${alert.symbol} ${VERBS[alert.condition]} ${level}: now ${price} ${alert.base}${per}` +
(alert.note ? ` (${alert.note})` : "");
// Les webhooks entrants de Slack attendent {"text": ...} ; ceux de Discord attendent {"content": ...}.
const body = process.env.CHAT_KIND === "discord" ? { content: text } : { text };
const res = await fetch(process.env.CHAT_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(10_000),
});
if (!res.ok) throw new Error(`chat webhook returned ${res.status}`);
}Pour l'exemple de payload, le message est « XAU rose above 4000 USD: now 4001.25 USD per troy_oz (Gold above 4k) ». Le même principe fonctionne avec tout ce qui dispose d'une API HTTP : ouvrir un ticket, rafraîchir les prix des produits mis en cache dans votre boutique lorsque l'or bouge de plus de 2 % en une journée, ou prévenir la personne qui gère un flux de prix. Exécutez le transfert dans un worker plutôt que dans le handler de la requête, afin qu'un service de messagerie lent ne puisse jamais faire expirer une livraison.
Pour tester votre endpoint sans attendre le marché, signez vous-même un exemple de payload avec votre secret :
SECRET="whsec_your_secret"
BODY='{"event":"alert.triggered","id":1,"created":1792765800,"alert":{"id":"00000000-0000-0000-0000-000000000000","symbol":"XAU","base":"USD","condition":"above","threshold":4000,"note":"test"},"price":4001.25,"unit":"troy_oz"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -i -X POST http://localhost:3000/webhooks/metalapi \
-H "Content-Type: application/json" \
-H "X-MetalAPI-Timestamp: $TS" \
-H "X-MetalAPI-Signature: t=$TS,v1=$SIG" \
--data-raw "$BODY"Un 204 signifie que la vérification de signature a réussi. Pour un test de bout en bout, créez une alerte dont la condition est déjà vraie, comme décrit plus haut, et regardez l'événement apparaître dans Recent deliveries. Les alertes livrées par webhook commencent au plan Advanced ; consultez les tarifs pour connaître les plans, ou créez un compte pour essayer d'abord les alertes par e-mail sur Lite.
Essayez avec vos propres données
Créez un compte gratuit et envoyez votre première requête en moins de deux minutes. 100 requêtes par mois, sans carte bancaire.
À lire aussi
API du cours de l'or : le guide complet pour les développeurs
Utiliser une API du cours de l'or : obtenir une clé, l'appeler en curl, JavaScript et Python, bien lire les taux, gérer historique, erreurs, quotas et cache.
Ajouter un widget de prix de l'or en direct à votre site web
Ajoutez un widget de prix de l'or en direct à WordPress, Shopify ou une page HTML : créez-le dans le tableau de bord MetalAPI, réglez-le et collez deux lignes.
Prix de l'or par carat : 24K, 22K, 18K et comment les calculer
Ce que signifient 24K, 22K et 18K, le lien avec les titres 916 et 750, comment calculer le prix au gramme de l'or par carat et l'obtenir via une API.