Zum Inhalt

Brand Partner API

Eine kleine, berechenbare REST API zum Erstellen von mintbot-Bestellungen, zum Abfragen ihres Status und zum Empfangen von Lifecycle-Ereignissen. JSON rein, JSON raus. Bearer-token auth. Idempotente Schreibvorgänge. Signierte Webhooks.

Dashboard für API-Zugriff Zu Webhooks springen


Auf einen Blick

Base URL https://mint.mintbot.ai/api/v1
Auth `Authorization: Bearer ***
Idempotency Idempotency-Key: <uuid> bei jedem POST
Content type application/json
Rate limit 120 req / 60 s pro Partner
Webhooks Signiert mit HMAC-SHA256, bis zu 7 Wiederholungsversuche

Authentifizierung

Jede Anfrage sendet einen Partner-API-key im Authorization header. Erstelle oder rotiere den key im Dashboard.

Authorization: Bearer mo_liv...xxxx
Content-Type: application/json

Rotation hat kein Karenzfenster

Das Rotieren des keys widerruft den vorherigen key atomar. Plane, den Wert in deiner Konfiguration zu tauschen, bevor du auf Rotate klickst.

Idempotenz

Jeder POST benötigt einen Idempotency-Key header. Jede UUID pro eindeutiger Anfrage funktioniert.

  • Wir cachen die Antwort für 24 Stunden. Ein erneuter Versuch mit demselben key spielt die ursprüngliche Antwort mit Idempotent-Replay: true erneut aus.
  • Wenn du denselben key mit einem anderen body wiederverwendest, kommt 409 idempotency_key_mismatch zurück.

Bestellungen

Bestellung erstellen

POST /orders

Erstellt eine Bestellung und gibt eine Stripe Checkout URL zurück. Sobald die Zahlung bestätigt ist, provisioniert mintbot den Agenten und das Umsatzereignis für den Partneranteil wird automatisch aufgezeichnet.

Anfrage

{
  "tier": "s1",
  "duration_months": 1,
  "credit_usd": 10,
  "language": "en",
  "external_id": "your-side-id",
  "success_url": "https://your.app/thanks?id={ORDER_ID}",
  "cancel_url":  "https://your.app/cart",
  "webhook_url": "https://your.app/mintbot-webhook"
}
tier
Einer von trial, s1, s2, s4.
duration_months
Kalendermonate Serverlaufzeit. Muss einer von 1, 3 oder 12 sein.
credit_usd
Optional. Vorab finanziertes Chat-Guthaben, das mit der Bestellung gebündelt ist.
language
Optional. Beeinflusst die Stripe Checkout locale und die Sprache der Willkommens-E-Mail.
external_id
Optional. Wird in jedem Webhook und jeder Bestellantwort zurückgegeben — nutze es, um mintbot-Bestellungen mit Zeilen in deinem eigenen System zu verknüpfen.
success_url · cancel_url
Rückkehr-URLs für Stripe Checkout. {ORDER_ID} wird serverseitig ersetzt.
webhook_url
Optional. Bestellspezifischer Override für die Webhook-URL auf Partnerebene.

Antwort — 201 Created

{
  "id": 42,
  "tier": "s1",
  "duration_months": 1,
  "credit_usd": 10,
  "amount_cents": 1200,
  "currency": "usd",
  "status": "awaiting_payment",
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_test_…",
  "panel_url": null,
  "expires_at": null,
  "language": "en",
  "external_id": "your-side-id",
  "created_at": "2026-05-16 09:58:00",
  "paid_at": null
}

Beispiel

curl -X POST https://mint.mintbot.ai/api/v1/orders \
  -H "Authorization: Bearer *** \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "tier": "s1",
    "duration_months": 1,
    "credit_usd": 10,
    "success_url": "https://your.app/thanks?id={ORDER_ID}",
    "cancel_url":  "https://your.app/cart"
  }'
import os, uuid, requests

r = requests.post(
    "https://mint.mintbot.ai/api/v1/orders",
    headers={
        "Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "tier": "s1",
        "duration_months": 1,
        "credit_usd": 10,
        "success_url": "https://your.app/thanks?id={ORDER_ID}",
        "cancel_url":  "https://your.app/cart",
    },
    timeout=10,
)
r.raise_for_status()
order = r.json()
redirect_to = order["checkout_url"]

Bestellung abrufen

GET /orders/{id}

Ruft eine einzelne Bestellung anhand ihrer mintbot id ab. Gibt dieselbe Struktur wie POST /orders zurück.

curl https://mint.mintbot.ai/api/v1/orders/42 \
  -H "Authorization: Bearer ***

Bestellungen auflisten

GET /orders

Cursor-paginierte Liste, neueste zuerst.

Query parameters

status
Optional. Filtere nach awaiting_payment, completed, deployed, deploy_failed oder expired.
cursor
Optional. Übergib next_cursor von der vorherigen Seite, um fortzufahren.

Antwort

{
  "items": [ /* OrderResponse … */ ],
  "next_cursor": "37"
}

next_cursor ist null, wenn es keine weiteren Seiten gibt.


Bestellung verlängern

POST /orders/{id}/renew

Verlängert einen bestehenden Agenten um weitere duration_months. Nur Infra — es wird kein neues Chat-Guthaben gebündelt. Gibt eine neue Bestell-id und eine frische Stripe Checkout URL zurück.

Anfrage

{
  "duration_months": 1,
  "external_id": "your-side-id",
  "success_url": "https://your.app/thanks?id={ORDER_ID}",
  "cancel_url":  "https://your.app/account"
}

Umsatz

Umsatz lesen

GET /revenue

Summen plus die neuesten 200 Ledger-Ereignisse.

Query parameters

include_paid
Optional, default true. Setze auf false, um nur Ereignisse zu sehen, die noch nicht ausgezahlt wurden.

Antwort

{
  "currency": "usd",
  "gross_cents": 12000,
  "partner_cut_cents": 2000,
  "mintbot_cut_cents": 10000,
  "unpaid_cents": 800,
  "events": [
    {
      "id": 7,
      "order_id": 42,
      "kind": "order_paid",
      "gross_cents": 1200,
      "partner_cut_cents": 200,
      "mintbot_cut_cents": 1000,
      "currency": "usd",
      "created_at": "2026-05-16 09:58:00",
      "payout_id": null,
      "payout_at": null
    }
  ]
}

Partnerprofil

Profil abrufen

GET /partner

Gibt dein Partnerprofil plus den nicht ausgezahlten Saldo zurück — praktisch, um Einnahmen in deiner eigenen Admin UI anzuzeigen, ohne sie selbst zu speichern.

Antwort

{
  "id": 12,
  "email": "you@kliendifirma.com",
  "pricing_currency": "usd",
  "balance_unpaid_cents": 800,
  "webhook_url": "https://your.app/mintbot-webhook",
  "api_key_prefix": "mo_live_a12b"
}

Einstellungen

Einstellungen abrufen

GET /settings

Gibt deine vollständige nicht geheime Partnerkonfiguration in einem einzigen Payload zurück — gewählte Modi, Apex-Domain, DNS-Provider, Bot-Provider, Template-Repo, Subdomain-Muster (mit einem gerenderten Vorschau-Label), Preise pro Tier, API-key-Metadaten, Webhook-Metadaten und denselben Readiness- / Pro-Sektion-Status, der das MintOffice-Dashboard-Banner steuert.

Konzipiert für Integrations-Agenten, die herausfinden müssen, wie der Partner eingerichtet ist, ohne das Dashboard zu scrapen oder den Betreiber zu befragen. Ein typischer Aufrufer ist ein Coding-Agent, der dein White-Label-Setup auf einem frischen Server installiert — er kann diesen Endpoint lesen, anhand von readiness.status verzweigen und dem Betreiber genau sagen, welche Sektionen noch Aufmerksamkeit brauchen.

Es wird niemals ein Geheimnis zurückgegeben

Alles, was der Partner als Anmeldedaten angegeben hat — Telegram-Bot-Token, Zone.ee API key, Webhook-Signaturgeheimnis, der API-key-Klartext selbst — erscheint nur als *_set boolean. Das Webhook-Geheimnis legt zusätzlich denselben 8-Zeichen-Anzeige-Präfix offen, der bereits im Dashboard angezeigt wird, sodass ein Aufrufer bestätigen kann, welches Geheimnis konfiguriert ist, ohne jemals den Wert zu sehen.

Antwort

{
  "id": 12,
  "email": "you@kliendifirma.com",
  "status": "live",
  "created_at": "2026-05-12 14:22:01",
  "onboarded_at": "2026-05-12 14:48:33",
  "activated_at": "2026-05-13 09:10:05",
  "last_login_at": "2026-05-20 00:51:18",
  "preferred_language": "en",
  "pricing_currency": "usd",
  "balance_unpaid_cents": 800,

  "domain": {
    "mode": "byo",
    "client_apex_domain": "agent99.cc",
    "dns_provider": "zone.ee",
    "zone_ee_username": "you@kliendifirma.com",
    "zone_ee_api_key_set": true,
    "subdomain_prefix": "agent",
    "subdomain_numbering": "natural",
    "subdomain_pad_width": 3,
    "subdomain_label_preview": "agent1"
  },

  "bot": {
    "mode": "own",
    "provider": "telegram",
    "telegram_bot_username": "agent99cc_bot",
    "telegram_bot_token_set": true
  },

  "template": {
    "mode": "default",
    "repo_url": null
  },

  "api": {
    "key_prefix": "mo_live_a12b",
    "key_created_at": "2026-05-13 09:11:42",
    "webhook_url": "https://your.app/mintbot-webhook",
    "webhook_secret_set": true,
    "webhook_secret_prefix": "ab12cd34"
  },

  "pricing": {
    "mode": "default",
    "currency": "usd",
    "tiers": {
      "trial": { "price_cents": 0,    "partner_cut_cents": 0,   "updated_at": null },
      "s1":    { "price_cents": 1200, "partner_cut_cents": 200, "updated_at": "2026-05-13 09:05:00" },
      "s2":    { "price_cents": 2400, "partner_cut_cents": 400, "updated_at": "2026-05-13 09:05:00" },
      "s4":    { "price_cents": 4800, "partner_cut_cents": 800, "updated_at": "2026-05-13 09:05:00" }
    }
  },

  "readiness": {
    "ready": true,
    "missing_fields": [],
    "dns_blocker": null,
    "status": "live"
  },

  "section_status": {
    "domain":   { "status": "ready", "mode": "byo",     "issue": null, "dns_blocker": null },
    "bot":      { "status": "ready", "mode": "own",     "issue": null, "dns_blocker": null },
    "template": { "status": "ready", "mode": "default", "issue": null, "dns_blocker": null },
    "api":      { "status": "ready", "mode": "live",    "issue": null, "dns_blocker": null },
    "all_ready": true
  }
}

Feldreferenz

status
Partner-Lifecycle. Einer von onboarding, live, paused. Die Brand Partner API lehnt Schreibvorgänge ab, solange onboarding.
domain.mode
byo (du bringst deine eigene Apex-Domain mit) oder hosted (mintbot stellt eine *.mintbot.ai-Subdomain bereit). Bei hosted sind die DNS-Provider-Felder absichtlich leer, unabhängig davon, was der Partner zuvor eingegeben hat.
domain.subdomain_label_preview
Das gerenderte Label, das der erste Kundenagent erhalten würde (agent1, 1, 001, …) angesichts des aktuellen subdomain_prefix, subdomain_numbering und subdomain_pad_width. Nutze es für eine "so sehen es deine Kunden"-Vorschau, ohne die Nummerierungsregeln neu zu implementieren.
bot.mode
own (vom Partner bereitgestellter Telegram-Bot), borrow (Nutzung von mintbots Bot während des Testens) oder web (nur Panel, kein Telegram).
template.mode
default (keine agent customization repo — der Agent läuft auf der schlichten ungebrandeten Basis) oder custom (die agent customization repo des Partners unter repo_url). Bei custom klont der eigene VPS des Agenten dieses Repo nach dem Standard-Deploy und führt sein install.sh / update.sh aus, um deine Persona, dein Panel-Theme und jegliches zusätzliche Tooling auf der Basis anzuwenden — central führt das Repo niemals selbst aus. Verweise repo_url auf deine GitHub-HTTPS-agent customization repo, wenn du eigenes Branding möchtest.
api.webhook_secret_prefix
Erste 8 Zeichen des Webhook-Signaturgeheimnisses, nur vorhanden, wenn ein Geheimnis konfiguriert ist. Lässt einen Aufrufer unterscheiden, welches Geheimnis vorliegt, ohne den vollständigen Wert zu sehen. Das Dashboard zeigt denselben Präfix.
readiness.missing_fields
Liste der Dashboard-Feldnamen, die vor Go Live noch erforderlich sind. Leer, wenn ready true ist. Stabil genug, um eine "was noch fehlt"-UI in deiner eigenen Admin zu treiben.
readiness.dns_blocker
Auf einen kurzen Grund-String gesetzt, wenn die DNS-Verifizierung fehlschlägt (z. B. nameservers_not_pointing, glue_record_missing). Andernfalls null.
section_status.*.issue
Pro Sektion menschenlesbarer Hinweis, wenn diese Sektion Go Live blockiert. Spiegelt die Pro-Sektion-Status-Pills des Dashboards wider.

Beispiel

curl https://mint.mintbot.ai/api/v1/settings \
  -H "Authorization: Bearer ***
import os, requests

r = requests.get(
    "https://mint.mintbot.ai/api/v1/settings",
    headers={"Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}"},
    timeout=10,
)
r.raise_for_status()
settings = r.json()

if not settings["readiness"]["ready"]:
    print("Still missing:", settings["readiness"]["missing_fields"])

Katalog

Die verkaufbare Paketliste für deinen Storefront — die öffentlichen Pakete (trial / starter / pro) verknüpft mit deiner aufgelösten Preisgestaltung, in deiner Preiswährung. Rendere deine Plan-Karten hieraus, statt Tier-Slugs und Preise fest zu codieren: ausgemusterte Pakete verschwinden von selbst, und eine Preis- oder Währungsänderung, die du im Dashboard vornimmst, erreicht deinen Storefront innerhalb seines Cache-Fensters — kein Redeploy.

Dies ist der Endpoint, der einen Storefront davon abhält, die Pakete von gestern zu bewerben. Das Referenz-Portal ruft ihn bei jedem Landing- / /buy- / /extend-Render auf (gecacht, mit einem Sticky-Fallback).

Anzeige-Text bleibt deiner

display_name / description / featured sind die kanonischen mintbot-Paketmetadaten. Wenn du ein Paket umbenennen oder seinen Beschreibungstext für deine Marke umschreiben möchtest, überschreibe ihn in deinem eigenen Storefront (das Referenz-Portal liefert genau dafür eine PLAN_OVERRIDES-Map) — der Katalog ist die Quelle der Wahrheit dafür, welche Pakete existieren und was sie kosten, nicht für deinen Marketing-Text.

Katalog abrufen

GET /api/v1/catalog

Keine Parameter. Nur Bearer-Auth — es gibt kein Aktivierungs-Gate, sodass ein Storefront den Katalog rendern kann, bevor dein Partneraccount auf live geschaltet wird.

Antwort

{
  "currency": "usd",
  "packages": [
    {
      "tier": "trial",
      "display_name": "Trial",
      "description": "One day to kick the tires.",
      "featured": false,
      "default_credit_usd": 5,
      "durations": [
        { "months": 1, "label": "24 hours", "price_cents": 0 }
      ],
      "subscription": { "available": false, "price_cents": null }
    },
    {
      "tier": "starter",
      "display_name": "Starter",
      "description": "A month of assistant time.",
      "featured": true,
      "default_credit_usd": 10,
      "durations": [
        { "months": 1,  "label": "1 month",   "price_cents": 1500 },
        { "months": 3,  "label": "3 months",  "price_cents": 4500 },
        { "months": 12, "label": "12 months", "price_cents": 18000 }
      ],
      "subscription": { "available": true, "price_cents": 1500 }
    }
  ]
}
Feld Bedeutung
currency Deine Preiswährung. Jeder price_cents unten ist darin denominiert.
packages[] Ein Eintrag pro öffentlichem Paket, in Angebots-/Anzeigereihenfolge.
tier Der Slug, den du an POST /orders / POST /subscriptions übergibst.
display_name / description / featured Kanonischer Anzeige-Text (überschreibe ihn in deinem Storefront, wenn du möchtest).
default_credit_usd Der gebündelte LLM-Guthaben-Hinweis des Pakets (informativ).
durations[] Die Setup-Laufzeiten, für die dieses Paket gekauft werden kann. months ist der Wert, den du als duration_months sendest; price_cents ist, was die Bestellung berechnet. Monatspakete bewerben 1 / 3 / 12 Monate; trial bewirbt eine einzelne 24-Stunden-Laufzeit.
subscription available ist true für Pakete, die als monatliche Auto-Verlängerung über POST /subscriptions gekauft werden können; price_cents ist der monatliche Betrag. trial ist niemals abonnementfähig.

Preise entsprechen dem, was dir berechnet wird

Jeder price_cents wird mit demselben Preis-Resolver berechnet, den der Bestell-Endpoint verwendet, sodass ein Karten-Preis immer dem letztlichen Stripe-Checkout-Gesamtbetrag für dieses (tier, duration_months) entspricht.

Beispiel

curl -s https://mint.mintbot.ai/api/v1/catalog \
  -H "Authorization: Bearer *** | jq
import os, requests

r = requests.get(
    "https://mint.mintbot.ai/api/v1/catalog",
    headers={"Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}"},
    timeout=10,
)
r.raise_for_status()
catalog = r.json()

for pkg in catalog["packages"]:
    month1 = next(d for d in pkg["durations"] if d["months"] == 1)
    print(pkg["display_name"], month1["price_cents"], catalog["currency"])

DNS-Einträge

Brand-Partner-Integrationen müssen oft DNS-Einträge auf der Apex-Domain des Partners provisionieren — @, www oder kundenindividuelle Subdomains auf den richtigen Server zeigen lassen. MintOffice stellt einen anbieterblinden Proxy bereit, sodass du dies tun kannst, ohne jemals den API key des Upstream-Anbieters in deiner eigenen Infrastruktur zu handhaben. Die Anmeldedaten, die du beim Onboarding gespeichert hast (heute: zone.ee; cloudflare und route53 sind hinter derselben Struktur eingeplant), bleiben verschlüsselt in MintOffice; dein Aufruf kommt mit Bearer mo_live_… herein und wir machen den Upstream-Aufruf in deinem Namen.

Der Geltungsbereich ist die eigene konfigurierte Apex des Partners (domain.client_apex_domain aus GET /settings). Du kannst keinen Eintrag in einer Zone berühren, die dir nicht gehört.

Heute unterstützt der Proxy die Operationen, die die Deploy-Pipeline selbst verwendet — A-Einträge (upsert / list / delete by id). CNAME- / TXT- / MX-Unterstützung wird derselben Struktur folgen, wenn die zugrunde liegende Provider-Abstraktion sie ergänzt.

Idempotency-Key hier nicht erforderlich

Anders als bei den Endpoints zur Bestellerstellung sind DNS-Upserts auf der Provider-Ebene natürlich idempotent (erneutes Ausführen mit demselben (subdomain, ip) ist ein No-op und veraltete Duplikate werden bei jedem Aufruf entfernt). Du musst den Idempotency-Key header nicht senden — Wiederholungsversuche konvergieren immer auf einen einzigen A-Eintrag pro FQDN.

Einen A-Eintrag upserten

POST /dns/records

Lässt <subdomain>.<your-apex> idempotent auf eine IPv4-Adresse zeigen. Übergib "" (oder "@") für die Apex selbst.

Anfrage

{
  "subdomain": "www",
  "ip": "203.0.113.10"
}

subdomain akzeptiert:

  • "" oder "@" — die Apex selbst (example.com).
  • Ein einzelnes DNS-Label — "www", "api" — für <label>.<apex>.
  • Punktierte Sub-Labels — "api.eu" — für verschachtelte Subdomains (api.eu.example.com).

ip muss eine punktierte IPv4-Adresse sein. Jedes Label ist [a-z0-9_-], 1–63 Zeichen, kein führendes/abschließendes -.

Antwort — 200

{
  "apex": "example.com",
  "provider": "zone_ee",
  "fqdn": "www.example.com",
  "records": [
    { "id": "9182734", "name": "www.example.com", "destination": "203.0.113.10" }
  ]
}

Die records-Liste nach dem Schreiben spiegelt immer die aktuelle Sicht des Upstream-Anbieters auf den FQDN nach einem Duplikat-Sweep wider — eine erfolgreiche Antwort mit genau einem Eintrag ist also das Happy-Path-Signal, dass nichts Veraltetes übrig bleibt.

Einträge auflisten

GET /dns/records

Gibt jeden A-Eintrag auf der konfigurierten Apex zurück. Füge ?name=<label-or-fqdn> hinzu, um auf einen FQDN zu filtern — der Wert kann ein bloßes Label ("www"), eine punktierte Subdomain ("api.eu") oder der vollständige FQDN ("www.example.com") sein.

Antwort — 200

{
  "apex": "example.com",
  "provider": "zone_ee",
  "items": [
    { "id": "9182734", "name": "example.com",     "destination": "203.0.113.10" },
    { "id": "9182735", "name": "www.example.com", "destination": "203.0.113.10" }
  ]
}

Einen Eintrag löschen

DELETE /dns/records/{record_id}

record_id ist der Anbieter-Identifikator, der von GET /dns/records zurückgegeben wird (zone.ee vergibt Integer-ids, an der API-Grenze in Strings umgewandelt). Das Löschen eines Eintrags, der nicht mehr existiert, wird als Erfolg behandelt — der Endpoint ist clientseitig idempotent.

Antwort — 200

{ "deleted": true, "id": "9182734" }

Fehler

HTTP error.code Ursache
401 unauthenticated / invalid_api_key Fehlender / falscher Bearer-Token.
422 validation_error Fehlerhafte Subdomain oder Nicht-IPv4-Ziel.
422 dns_not_configured Partner hat keine client_apex_domain oder die Anmeldedaten des konfigurierten Anbieters fehlen. Behebe es in Settings → Domain.
422 dns_provider_unsupported Der DNS-Provider des Partners ist noch nicht in den MintOffice-Proxy eingebunden (z. B. cloudflare-Platzhalter). Wechsle vorerst zu zone_ee.
502 dns_provider_error Upstream-Anbieter gab einen 4xx/5xx zurück oder der Transport ist fehlgeschlagen. Die error message trägt den Upstream-Status und einen kurzen, geheimnis-redigierten Auszug des Upstream-Body. Sicher zum Wiederholen.

Beispiel

# Point apex + www at your server's IP.
curl https://mint.mintbot.ai/api/v1/dns/records \
  -H "Authorization: Bearer *** \
  -H "Content-Type: application/json" \
  -d '{"subdomain": "",    "ip": "203.0.113.10"}'

curl https://mint.mintbot.ai/api/v1/dns/records \
  -H "Authorization: Bearer *** \
  -H "Content-Type: application/json" \
  -d '{"subdomain": "www", "ip": "203.0.113.10"}'

# Verify.
curl "https://mint.mintbot.ai/api/v1/dns/records" \
  -H "Authorization: Bearer ***
import os, requests

URL = "https://mint.mintbot.ai/api/v1/dns/records"
H = {"Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}"}

for sub in ("", "www"):
    r = requests.post(URL, headers=H, json={"subdomain": sub, "ip": "203.0.113.10"})
    r.raise_for_status()
    print(sub or "@", r.json()["records"])

Webhooks

Wenn etwas mit einer Bestellung passiert, senden wir ein signiertes JSON-Ereignis per POST an deine konfigurierte Webhook URL.

Ereignistypen

Event Wann es ausgelöst wird
order.created Stripe Checkout session wurde erstellt, Zahlung ausstehend.
order.paid Stripe hat die Zahlung bestätigt. Umsatzereignis wurde aufgezeichnet.
order.cancelled Bestellung ist abgelaufen oder wurde ausdrücklich storniert.
agent.provisioning_started Deploy pipeline für diese Bestellung wurde gestartet.
agent.ready Deploy erfolgreich. Payload enthält panel_url und expires_at.
agent.failed Deploy pipeline ist fehlgeschlagen. Prüfe das Feld error für den defekten Schritt.
agent.expired Agent TTL ist abgelaufen. Der Partner kann über POST /orders/{id}/renew verlängern.

Zustellung & Wiederholungsversuche

  • Bis zu 7 Versuche nach exponentiellem Zeitplan: 0s, 30s, 2m, 10m, 1h, 6h, 24h.
  • Nach dem letzten Versuch wird die Zustellung als exhausted markiert und nicht weiter wiederholt.
  • Antworte innerhalb von 10 Sekunden mit einem 2xx status, um zu bestätigen.

Request headers

Content-Type: application/json
User-Agent: mintbot-webhook/1.0
X-Mintbot-Signature: t=<unix_ts>,v1=<hex_hmac_sha256>
X-Mintbot-Event-Id: evt_42_order.paid_1747371234567
X-Mintbot-Event-Type: order.paid

Beispiel-Payload — order.paid

{
  "id": 42,
  "tier": "s1",
  "duration_months": 1,
  "credit_usd": 10,
  "amount_cents": 1200,
  "currency": "usd",
  "status": "completed",
  "external_id": "your-side-id",
  "paid_at": "2026-05-16 10:02:14"
}

Signatur verifizieren

Die Signatur ist HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Verifiziere immer den rohen request body — wenn du dein geparstes JSON erneut serialisierst, bricht der Vergleich.

import hmac, hashlib, time

def verify(secret: str, body: bytes, header: str, tolerance: int = 300) -> bool:
    try:
        ts_part, v1_part = header.split(",", 1)
        ts  = int(ts_part.split("=", 1)[1])
        sig = v1_part.split("=", 1)[1]
    except Exception:
        return False
    if abs(int(time.time()) - ts) > tolerance:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{ts}.".encode() + body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, sig)
const crypto = require("crypto");

function verify(secret, rawBody, header, toleranceSec = 300) {
  const [tsPart, v1Part] = header.split(",");
  const ts  = Number(tsPart.split("=")[1]);
  const sig = v1Part.split("=")[1];
  if (Math.abs(Date.now() / 1000 - ts) > toleranceSec) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.`)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(sig, "hex"),
  );
}

Idempotente Empfänger

Nutze X-Mintbot-Event-Id als deinen Dedupe-key — Wiederholungsversuche verwenden dieselbe id wieder, sodass ein zeilenweises INSERT … ON CONFLICT DO NOTHING auf dieser Spalte deinen Handler sicher hält.


Fehler

Jede Fehlerantwort enthält einen stabilen code, nach dem du programmatisch verzweigen kannst. Das Feld message ist ein menschenlesbarer Hinweis und kann sich zwischen Releases ändern. request_id spiegelt den X-Request-Id response header wider — füge ihn Support-Tickets hinzu.

{
  "error": {
    "code": "invalid_api_key",
    "message": "API key is unknown or revoked.",
    "request_id": "f0c2d6c4-…"
  }
}
Code Bedeutung
unauthenticated Fehlender oder fehlerhafter Authorization header.
invalid_api_key API key ist unbekannt oder wurde herausrotiert.
rate_limited Rate limit pro Partner oder pro IP überschritten — siehe Retry-After.
missing_idempotency_key POST request ohne Idempotency-Key.
idempotency_key_mismatch Derselbe key wurde mit einem anderen request body wiederverwendet.
validation_error Request body hat die Schema-Validierung nicht bestanden.
not_found Ressource existiert nicht oder gehört nicht zu deinem Partner.
payment_gateway_error Stripe hat die Erstellung der Checkout session abgelehnt.

Rate limits

  • 120 requests / 60 s rolling window, pro Partner. Burst und steady-state teilen sich denselben bucket.
  • 60 requests / 60 s separater bucket pro IP für nicht authentifizierte und fehlgeschlagene Auth-Anfragen — verhindert, dass ein Bearer brute-force die Partnerquote aufbraucht.
  • Überschüssige Anfragen geben 429 rate_limited mit einem Retry-After header zurück (Sekunden zum Zurückziehen).

Brauchst du Hilfe?

Diese Dokumentation ist für Partner geschrieben, die die API wirklich nutzen. Wenn etwas fehlt, unklar oder veraltet ist, erwähne es gegenüber deinem mintbot-Agenten — er leitet das Feedback weiter und wir aktualisieren die Seite.