Gold- und Silberpreisalarme mit Webhooks automatisieren
Gold- und Silberpreisalarme mit signierten Webhooks automatisieren: Bedingungen, Sperrfristen, Payload, HMAC-SHA256-Prüfung in Node und Python sowie Wiederholungen.
Alarme statt Polling
Viele Integrationen interessieren sich nur zu bestimmten Zeitpunkten für Preise: wenn Gold eine Marke überschreitet, nach der ein Kunde gefragt hat, wenn Silber stark fällt oder wenn sich eine Währung so weit bewegt, dass ein Katalog neu bepreist werden muss. Wer /latest einmal pro Minute abfragt, um diese Momente zu erwischen, verbraucht etwa 45.000 Anfragen im Monat und muss die Logik, die das Kreuzen der Schwelle erkennt, trotzdem selbst schreiben.
Preisalarme von MetalAPI verlagern diese Arbeit auf den Server. Die Alarm-Engine prüft jeden aktiven Alarm gegen die Live-Preise, sobald sie eintreffen, und benachrichtigt Sie, wenn eine Bedingung erfüllt ist. Alarmprüfungen verbrauchen kein API-Kontingent, und Ihr Code läuft nur, wenn tatsächlich etwas passiert.
| Tarif | Alarme | Zustellung |
|---|---|---|
| Free | Keine | — |
| Lite | 3 | |
| Standard | 10 | |
| Advanced | 25 | E-Mail oder Webhook |
| Pro | 50 | E-Mail oder Webhook |
| Ultra | 100 | E-Mail oder Webhook |
| Business | 200 | E-Mail oder Webhook |
| Enterprise | 500 | E-Mail oder Webhook |
Sie verwalten Alarme im Dashboard unter Alerts. Die Webhook-Anleitung ist die kurze Referenz; dieser Artikel behandelt die Details, die Sie für einen Endpunkt im Produktivbetrieb brauchen. Wenn Sie jede Preisaktualisierung benötigen statt nur Schwellenwert-Ereignisse, beschreibt der Artikel zum Echtzeit-Streaming den anderen Ansatz.
Alarmbedingungen und wann sie auslösen
Jeder Alarm überwacht ein Symbol, bepreist in einer Währung, zum Beispiel XAU in USD oder XAG in EUR. Jedes aktive Symbol ist möglich, einschließlich Platin, Palladium, Industriemetallen, Energie, Währungen und Krypto. Es gibt vier Bedingungen:
| Bedingung | Löst aus, wenn | Schwellenwert |
|---|---|---|
above | Preis ≥ Schwellenwert | Ein Preis in der Währung des Alarms |
below | Preis ≤ Schwellenwert | Ein Preis in der Währung des Alarms |
change_pct_up | Veränderung seit dem vorherigen Schlusskurs ≥ Schwellenwert | Ein Prozentsatz unter 100 |
change_pct_down | Veränderung seit dem vorherigen Schlusskurs ≤ −Schwellenwert | Ein Prozentsatz unter 100, als positive Zahl eingegeben |
Schwellenwerte für Metalle gelten pro Feinunze, der kanonischen Einheit. Das gilt auch für Industriemetalle wie Kupfer, die anderswo oft pro Pfund notiert werden. Energie verwendet die jeweilige Kontrakteinheit, bei Rohöl ein Barrel, und der Webhook-Payload gibt die Einheit immer an.
Preise und prozentuale Veränderungen werden in der Währung des Alarms berechnet. Bei XAG in EUR kann allein ein stärkerer Euro Silber unter Ihren Schwellenwert drücken oder change_pct_down auslösen, selbst wenn Silber in US-Dollar unverändert ist.
Alarme lösen beim Kreuzen der Schwelle aus, nicht bei jeder Preisaktualisierung, solange die Bedingung erfüllt ist. Steigt Gold über 4.000 $, erhalten Sie eine Benachrichtigung. Der Alarm wird wieder scharf geschaltet, sobald die Bedingung zwischenzeitlich nicht mehr erfüllt war. Fällt der Preis also später unter die Marke und steigt erneut darüber, werden Sie wieder benachrichtigt.
Die Sperrfrist (Cooldown) ist die Mindestzeit zwischen zwei Benachrichtigungen desselben Alarms: zwischen 5 Minuten und 7 Tagen (10.080 Minuten), standardmäßig 60 Minuten. Ein Preis, der um einen Schwellenwert pendelt, kann ihn innerhalb einer Stunde viele Male kreuzen; die Sperrfrist macht daraus eine einzige Nachricht. Für eine Marke, die nur einmal wichtig ist, wählen Sie eine lange Sperrfrist und pausieren den Alarm, nachdem er ausgelöst hat.
Ein Verhalten ist beim Testen praktisch. Ein neuer Alarm hat keinen vorherigen Zustand. Ist seine Bedingung bereits erfüllt, löst er deshalb mit der nächsten Preisaktualisierung aus, bei laufendem Handel meist innerhalb einer Minute. Ein Alarm für XAU über 1 USD mit einer Sperrfrist von 5 Minuten liefert Ihnen eine echte Zustellung zum Testen. Löschen Sie ihn danach wieder.
Zustellung per E-Mail oder Webhook
E-Mail-Alarme sind ab dem Lite-Tarif verfügbar. Sie richten sich an Menschen: ein Betreff wie „MetalAPI alert: Gold (XAU) rose above 4000 USD“, der aktuelle Preis und die Notiz, die Sie dem Alarm mitgegeben haben.
Webhooks, verfügbar ab dem Advanced-Tarif, richten sich an Systeme: ein HTTPS-POST mit JSON-Body an eine URL Ihrer Wahl, signiert, damit Sie nachweisen können, dass er von MetalAPI stammt. Eine Webhook-URL muss:
https://verwenden,- auf eine öffentliche IP-Adresse auflösen (private, Loopback- und Link-Local-Bereiche werden beim Speichern des Alarms abgelehnt und bei der Zustellung erneut geprüft),
- ohne Benutzernamen und Passwort auskommen.
Weiterleitungen werden nicht verfolgt, geben Sie also die endgültige URL ein. Wechselt ein Konto in einen Tarif ohne Webhooks, werden seine Webhook-Alarme per E-Mail zugestellt statt verworfen.
Alle Webhooks eines Kontos werden mit einem einzigen Secret signiert, das mit whsec_ beginnt. Sie können es im Bereich Webhook signing secret auf der Alerts-Seite erstellen, anzeigen, kopieren und rotieren. Haben Sie noch keines erstellt, wird es bei der ersten Webhook-Zustellung erzeugt und danach dort angezeigt. Eine Rotation wirkt sofort, was für den Prüfcode weiter unten wichtig ist.
Die Webhook-Anfrage
Jede Zustellung sieht so aus:
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"
}| Feld | Bedeutung |
|---|---|
event | Derzeit immer alert.triggered. Ignorieren Sie unbekannte Werte, damit neue Ereignistypen Ihren Handler nie aus dem Tritt bringen. |
id | Numerische Ereignis-ID, eindeutig pro ausgelöstem Alarm und bei Wiederholungen identisch |
created | Unix-Zeit, zu der der Alarm ausgelöst hat |
alert | Der Alarm wie konfiguriert: id (eine UUID), symbol, base, condition, threshold und note |
price | Der Preis, der den Alarm ausgelöst hat, in base pro unit |
unit | troy_oz für Metalle, bbl, mmbtu oder gal für Energie, currency für Währungen, coin für Krypto |
Das Feld note mit bis zu 200 Zeichen eignet sich gut für Routing-Informationen, die Sie im Dashboard festlegen, etwa einen Teamnamen, einen Kanal oder eine Artikelnummer (SKU).
Signatur in Node.js prüfen
Der Header X-MetalAPI-Signature hat die Form t=<timestamp>,v1=<signature>. Die Signatur ist der HMAC-SHA256 der Zeichenkette <timestamp>.<raw body> als Hex in Kleinbuchstaben, berechnet mit Ihrem whsec_-Secret als Schlüssel. Der Zeitstempel wird zusätzlich separat in X-MetalAPI-Timestamp gesendet. Drei Details entscheiden darüber, ob die Prüfung im Produktivbetrieb funktioniert:
- Die Rohbytes hashen. Wenn Sie das JSON parsen und erneut serialisieren, können sich Leerzeichen oder Zahlenformate ändern, und die Signatur stimmt nicht mehr. Lesen Sie den Body als Buffer.
- Den Zeitstempel aus dem Header prüfen. Verwenden Sie
taus dem Header, nichtcreatedaus dem Body. Jede Wiederholung wird beim Senden mit einem neuentneu signiert, währendcreateddie ursprüngliche Zeit behält. Ein Replay-Fenster auf Basis voncreatedwürde gültige Wiederholungen ablehnen. - Während einer Rotation zwei Secrets akzeptieren. Eine Rotation im Dashboard stellt die Signierung sofort auf das neue Secret um. Fügen Sie das neue Secret neben dem alten hinzu und deployen Sie; Zustellungen, die in der Zwischenzeit fehlschlagen, werden einige Minuten lang wiederholt. Entfernen Sie das alte Secret, sobald das neue funktioniert.
Mit Express liefert express.raw() den Body als Buffer:
// webhook-server.mjs — Node.js 18+, npm install express
import crypto from "node:crypto";
import express from "express";
// Kommagetrennt: das aktuelle Secret, während einer Rotation zusätzlich das vorherige.
const SECRETS = (process.env.METALAPI_WEBHOOK_SECRETS ?? "").split(",").filter(Boolean);
const TOLERANCE_S = 300; // Signaturen ablehnen, die älter als 5 Minuten sind
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); // innerhalb von 8 Sekunden antworten, dann die Arbeit erledigen
if (event.event === "alert.triggered") {
handleAlert(event).catch((err) => console.error("alert handler failed:", err));
}
});
app.listen(3000);Der Handler antwortet, bevor er irgendetwas erledigt. Ist die Arbeit wichtig, speichern Sie das Ereignis zuerst dauerhaft (wie, zeigen die nächsten Abschnitte), damit ein Absturz nach der Antwort es nicht verlieren kann.
Signatur in Python prüfen
Dieselbe Prüfung in Python kommt mit der Standardbibliothek aus. hmac.compare_digest sorgt für den Vergleich in konstanter Zeit:
# webhook_app.py — pip install flask, dann: flask --app webhook_app run --port 3000
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, request
# Kommagetrennt: das aktuelle Secret, während einer Rotation zusätzlich das vorherige.
SECRETS = [s for s in os.environ.get("METALAPI_WEBHOOK_SECRETS", "").split(",") if s]
TOLERANCE_S = 300 # Signaturen ablehnen, die älter als 5 Minuten sind
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() # Rohbytes, vor jedem JSON-Parsing
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"]
# Hier das Ereignis an eine Job-Queue übergeben und schnell antworten.
app.logger.warning("%s/%s %s %s: %s", alert["symbol"], alert["base"],
alert["condition"], alert["threshold"], event["price"])
return "", 204Andere Frameworks unterscheiden sich nur darin, wie Sie den Roh-Body lesen: await request.body() in FastAPI und request.body in Django. Stellen Sie unabhängig vom Framework sicher, dass keine Middleware den Body geparst und verbraucht hat, bevor Ihr Handler läuft.
Wiederholungen, Duplikate und Antwortzeiten
Eine Zustellung ist erfolgreich, wenn Ihr Endpunkt innerhalb von 8 Sekunden mit einem beliebigen 2xx-Status antwortet. Timeouts, Verbindungsfehler, Weiterleitungen und alle anderen Statuscodes gelten als Fehlschlag und werden wiederholt:
| Versuch | Zeitpunkt |
|---|---|
| 1 | Sobald der Alarm auslöst |
| 2 | Etwa 1 Minute nach dem Auslösen |
| 3 | Etwa 4 Minuten nach dem Auslösen |
Nach drei Versuchen wird das Ereignis als fehlgeschlagen markiert. Die Tabelle Recent deliveries auf der Alerts-Seite listet jedes Ereignis mit seinem Ergebnis auf, einschließlich HTTP-Status oder Fehlermeldung des letzten Versuchs. Dort sollten Sie zuerst nachsehen, wenn sich ein Endpunkt merkwürdig verhält.
Wiederholungen verwenden dieselbe Ereignis-id. Eine Zustellung, die Ihr Server zwar verarbeitet, aber erst nach mehr als 8 Sekunden beantwortet hat, gilt bei MetalAPI als fehlgeschlagen und kommt erneut an. Der Handler muss daher idempotent sein. Am einfachsten geht das mit einer Tabelle, deren Schlüssel die Ereignis-ID ist:
create table metalapi_events (
event_id bigint primary key, -- "id" aus dem Payload, bei Wiederholungen identisch
alert_id uuid not null,
payload jsonb not null,
received_at timestamptz not null default now(),
processed_at timestamptz
);
-- Im Webhook-Handler, nach der Signaturprüfung und vor der Antwort:
insert into metalapi_events (event_id, alert_id, payload)
values ($1, $2, $3)
on conflict (event_id) do nothing;Fügen Sie das Ereignis ein, antworten Sie mit 204 und lassen Sie einen Worker die Zeilen verarbeiten, bei denen processed_at null ist. Eine doppelte Zustellung trifft auf den Primärschlüssel und ändert nichts. Das Insert dauert Millisekunden, sodass Sie selbst bei langsamer Folgearbeit deutlich innerhalb des 8-Sekunden-Fensters bleiben.
Alarme an Chat und andere Systeme weiterleiten
Ein häufiger Anwendungsfall ist das Posten von Alarmen in einen Team-Chat. Slack und Discord akzeptieren beide eingehende Webhooks, aber richten Sie einen MetalAPI-Alarm nicht direkt auf sie: Sie erwarten einen anderen Payload, und Sie würden die Signaturprüfung verlieren. Prüfen Sie auf Ihrem eigenen Endpunkt, formatieren Sie dann und leiten Sie weiter. Diese Version von handleAlert ersetzt den Logging-Platzhalter im Node.js-Server oben:
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})` : "");
// Eingehende Slack-Webhooks erwarten {"text": ...}; Discord-Webhooks erwarten {"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}`);
}Für den Beispiel-Payload lautet die Nachricht „XAU rose above 4000 USD: now 4001.25 USD per troy_oz (Gold above 4k)“. Dasselbe Muster funktioniert mit allem, was eine HTTP-API hat: ein Ticket eröffnen, gecachte Produktpreise in Ihrem Shop aktualisieren, wenn sich Gold an einem Tag um mehr als 2 % bewegt, oder die Person benachrichtigen, die einen Preisfeed pflegt. Führen Sie die Weiterleitung in einem Worker aus statt im Request-Handler, damit ein langsamer Chat-Dienst eine Zustellung nie in einen Timeout laufen lässt.
Um Ihren Endpunkt zu testen, ohne auf den Markt zu warten, signieren Sie einen Beispiel-Payload selbst mit Ihrem 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"Ein 204 bedeutet, dass die Signaturprüfung bestanden ist. Für einen End-to-End-Test erstellen Sie, wie oben beschrieben, einen Alarm, dessen Bedingung bereits erfüllt ist, und beobachten, wie das Ereignis unter Recent deliveries erscheint. Alarme mit Webhook-Zustellung gibt es ab dem Advanced-Tarif; die Tarife finden Sie auf der Preisseite, oder erstellen Sie ein Konto, um zunächst E-Mail-Alarme in Lite auszuprobieren.
Probieren Sie es mit Ihren eigenen Daten aus
Erstellen Sie ein kostenloses Konto und senden Sie Ihre erste Anfrage in unter zwei Minuten. 100 Anfragen pro Monat, keine Kreditkarte.
Weiterlesen
Goldpreis-API: Der vollständige Leitfaden für Entwickler
So nutzen Sie eine Goldpreis-API: Schlüssel holen, Aufrufe mit curl, JavaScript und Python, Kurse richtig lesen sowie Historie, Fehler, Kontingente und Caching.
So binden Sie ein Live-Goldpreis-Widget in Ihre Website ein
Live-Goldpreis-Widget für WordPress, Shopify oder HTML: im MetalAPI-Dashboard erstellen, Symbole, Layout und Design wählen und zwei Zeilen Code einfügen.
Goldpreise nach Karat erklärt: 24K, 22K, 18K und wie man sie berechnet
Was 24, 22 und 18 Karat bedeuten, wie Feingehalte wie 916 und 750 zusammenhängen, wie Sie Karatpreise pro Gramm berechnen und per Goldpreis-API abrufen.