Vom Agenten angestoßene, vom Menschen genehmigte Abonnements
- Für wen
- Entwickler von KI-Agenten, deren Inhaber noch keinen Cold-Leads-Schlüssel hat
- Das Problem
- Ein Agent, der E-Mail-Prüfung braucht, sollte nie eine Karte besitzen oder selbst ein Abonnement abschließen. Den Menschen loszuschicken, damit er sich registriert, einen Tarif wählt und einen Schlüssel kopiert, unterbricht die Aufgabe, und in Chats eingefügte Schlüssel werden offengelegt.
- Die Lösung
- Der Agent ruft ohne Schlüssel einen Endpunkt auf und erhält einen Stripe-Checkout-Link für seinen Inhaber sowie ein geheimes Claim-Token. Der Inhaber prüft den Tarif und entscheidet. Nach der Zahlung holt der Agent den API-Schlüssel mit dem Claim-Token genau einmal ab, per Polling oder nach einem signierten Callback.
- Was Sie bekommen
- Ein funktionierender geheimer API-Schlüssel für das neue Business-Konto des Inhabers, einmal an den Agenten ausgeliefert, und eine E-Mail an den Inhaber mit einem Link, der die Cold-Leads-Web-App öffnet.
Adressen, IDs und Ergebnisse in den Beispielen sind illustrativ. example.com ist für Dokumentation reserviert, eine echte Prüfung dieser Adressen liefert daher invalid.
Die Codebeispiele sind in allen Sprachen gleich, ihre Kommentare sind auf Englisch.
Der Ablauf im Überblick
Agent (no key) Cold Leads Human owner Stripe
| | | |
| POST /api/agent/provision | | |
| owner_email, agent_id, | | |
| callback_url (optional) | | |
|----------------------------->| pending account, disabled key, | |
| | Checkout session (24 h) --------------------------------->|
|<-----------------------------| checkout_url, session_id, | |
| | claim_token (shown once) | |
| shows checkout_url --------------------------------------------->| |
| | | reviews plan, pays->|
| |<-------------------------------------- payment webhook --|
| | activates account and key, | |
| | e-mails the owner a sign-in link ->| |
|<-- signed callback (optional, no secrets) -----------------------| |
| GET /api/agent/status | | |
| ?session_id=... | | |
| X-Claim-Token: clt_... | | |
|----------------------------->| | |
|<-----------------------------| status active + api_key | |
| | (first call only) | |Derselbe Ablauf ist im Node.js-SDK (agent.provision, agent.waitForActivation, verifyCallbackSignature) und im lokalen stdio-MCP-Server als Tools provision_account_and_get_payment_link und check_provisioning_status eingebaut. Der gehostete MCP-Endpunkt braucht bei jeder Anfrage einen Schlüssel; ein Agent ohne Schlüssel nutzt für diesen Schritt daher REST, das SDK oder den stdio-Server.
Schritt 1: Zahlungslink anfordern (kein Schlüssel nötig)
owner_email ist der Mensch, dem das Konto gehören und der es bezahlen wird; agent_id benennt Ihren Agenten oder Ihr Produkt und wird auf der Zahlungsseite angezeigt. callback_url ist optional und muss https auf einem öffentlichen Host sein.
curl -s https://coldleads.app/api/agent/provision \
-H "Content-Type: application/json" \
-d '{
"owner_email": "owner@example.com",
"agent_id": "Acme Research Agent",
"callback_url": "https://agent.example.com/hooks/coldleads"
}'{
"status": "payment_required",
"checkout_url": "https://checkout.stripe.com/c/pay/cs_live_…",
"session_id": "cs_live_…",
"provision_id": "prov_…",
"claim_token": "clt_…",
"expires_at": "2026-10-01T09:30:00.000Z",
"plan": { "id": "business", "name": "Business", "price": "$99", "amount": 99, "currency": "usd", "interval": "month", "api": true },
"status_url": "https://coldleads.app/api/agent/status?session_id=cs_live_…",
"instructions_for_agent": "Show the human owner (owner@example.com) this payment link and let them decide: … Do not open, pay or forward the link yourself. Keep claim_token secret (it is shown only now). …"
}| HTTP | error | Wann |
|---|---|---|
| 400 | invalid_owner_email, agent_id_required, invalid_callback_url, bad_body | Der Body ist kein gültiges JSON, die E-Mail-Adresse des Inhabers besteht die Syntaxprüfung nicht, agent_id ist leer oder callback_url ist nicht https auf einem öffentlichen Host. |
| 409 | account_exists | Zu dieser E-Mail-Adresse gibt es bereits einen Cold-Leads-Arbeitsbereich. Bitten Sie den Inhaber stattdessen um einen geheimen Schlüssel aus Einstellungen → API-Schlüssel. |
| 429 | rate_limited | Mehr als 10 Anfragen pro Stunde von einer IP-Adresse oder mehr als 3 Anfragen pro Tag für dieselbe E-Mail-Adresse des Inhabers. |
| 502 | gateway | Stripe konnte den Checkout nicht anlegen; versuchen Sie es später erneut. |
Schritt 2: Der Inhaber entscheidet bei Stripe
- Zeigen Sie dem Menschen checkout_url und lassen Sie ihn entscheiden. Den Link nicht selbst öffnen, bezahlen oder weiterleiten.
- Die Stripe-Seite nennt das Produkt Cold Leads Business (API access) und gibt den Namen des Agenten, den Preis von $99 pro Monat, die monatliche Verlängerung bis zur Kündigung sowie Links zu den Bedingungen an. Auf diesem Weg gibt es keine Testphase, und beim Checkout können Steuern hinzukommen.
- Der Link ist 24 Stunden gültig. Danach wechselt der Status auf expired, und der Agent kann einen neuen Link anfordern.
- Sobald die Zahlung durch ist, aktiviert Cold Leads Konto und Schlüssel und schickt dem Inhaber per E-Mail einen einmaligen Link zur Web-App, wo er das Abonnement verwalten oder den Schlüssel des Agenten erneuern kann.
Schritt 3: Den Schlüssel einmal abholen
Fragen Sie GET /api/agent/status?session_id=… mit dem Header X-Claim-Token alle 15 bis 30 Sekunden ab, solange der Status pending_payment ist. Die erste Antwort nach der Zahlung auf eine Anfrage mit gültigem Claim-Token enthält api_key; keine spätere Antwort enthält ihn.
| status | Felder neben session_id und plan |
|---|---|
| pending_payment | expires_at; checkout_url, wenn das Claim-Token gültig ist |
| active (erster Aufruf mit dem Claim-Token) | activated_at, api_key, api_key_prefix, mcp_url, api_base, message |
| active (jeder spätere Aufruf) | activated_at, api_key_prefix, mcp_url, api_base, key_delivered_at, message |
| active (ohne gültiges Claim-Token) | nur activated_at |
| expired | message: einen neuen Link mit POST /api/agent/provision anfordern |
| suspended | keine |
{
"session_id": "cs_live_…",
"plan": "business",
"status": "active",
"activated_at": "2026-09-30T10:02:11.000Z",
"api_key_prefix": "sk_…9f3c",
"mcp_url": "https://coldleads.app/api/mcp",
"api_base": "https://coldleads.app/api/v1",
"api_key": "sk_…",
"message": "This is the only time the key is shown. Store it as COLDLEADS_API_KEY; send it as Authorization: Bearer <key>."
}# pip install httpx
import os
import time
import httpx
BASE = "https://coldleads.app"
SESSION_ID = os.environ["COLDLEADS_SESSION_ID"] # session_id from the provision response
CLAIM_TOKEN = os.environ["COLDLEADS_CLAIM_TOKEN"] # claim_token (clt_...), shown only once
def save_key(key: str) -> None:
path = os.path.expanduser("~/.coldleads_api_key") # or your secret manager
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w") as f:
f.write(key)
while True:
r = httpx.get(f"{BASE}/api/agent/status", params={"session_id": SESSION_ID},
headers={"X-Claim-Token": CLAIM_TOKEN}, timeout=30)
if r.status_code == 429: # 120 status requests per minute per IP
time.sleep(60)
continue
r.raise_for_status()
s = r.json()
if s["status"] == "active":
if "api_key" not in s:
raise SystemExit("The key was already collected; the owner can rotate it in Settings -> API keys.")
save_key(s["api_key"])
print("API key saved; prefix", s["api_key_prefix"])
break
if s["status"] in ("expired", "suspended"):
raise SystemExit(s.get("message", s["status"]))
time.sleep(20) # pending_payment: poll every 15-30 secondsOptional: signierter Callback statt Polling
- Hat der Inhaber bezahlt, sendet Cold Leads einen einzigen POST an callback_url mit 5 Sekunden Timeout. Weiterleitungen werden nicht verfolgt und fehlgeschlagene Zustellungen nicht wiederholt; behalten Sie Polling daher als Rückfallebene.
- Der JSON-Body enthält event (coldleads.account.activated), provision_id, session_id, status (active) und status_url. Den Schlüssel enthält er nie: Holen Sie ihn mit der Statusanfrage aus Schritt 3 ab.
- Header: X-Coldleads-Event mit dem Namen des Events und X-Coldleads-Signature mit sha256=, gefolgt von einem Hex-Digest: HMAC-SHA256 über den rohen Request-Body, mit dem SHA-256-Hex-Digest Ihres Claim-Tokens als Schlüssel.
// npm install express
import express from "express";
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
const CLAIM_TOKEN = process.env.COLDLEADS_CLAIM_TOKEN; // clt_... from the provision response
// X-Coldleads-Signature: sha256=<hex HMAC-SHA256 of the raw body>, key = SHA-256 hex digest of the claim token
function validSignature(rawBody, header, claimToken) {
if (!Buffer.isBuffer(rawBody) || typeof header !== "string" || !header.startsWith("sha256=")) return false;
const key = createHash("sha256").update(claimToken).digest("hex");
const expected = createHmac("sha256", key).update(rawBody).digest();
const given = Buffer.from(header.slice("sha256=".length), "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}
const app = express();
app.post("/hooks/coldleads", express.raw({ type: "application/json" }), (req, res) => {
if (!validSignature(req.body, req.get("x-coldleads-signature"), CLAIM_TOKEN)) return res.sendStatus(401);
const event = JSON.parse(req.body.toString("utf8"));
res.sendStatus(204); // answer fast: Cold Leads waits 5 seconds and does not retry
if (event.event === "coldleads.account.activated") {
// the callback carries no key: collect it now with GET /api/agent/status + X-Claim-Token
console.log("owner paid, session", event.session_id);
}
});
app.listen(3000);# pip install flask
import hashlib
import hmac
import json
import os
from flask import Flask, request
CLAIM_TOKEN = os.environ["COLDLEADS_CLAIM_TOKEN"] # clt_... from the provision response
app = Flask(__name__)
def valid_signature(raw_body: bytes, header, claim_token: str) -> bool:
if not header or not header.startswith("sha256="):
return False
key = hashlib.sha256(claim_token.encode()).hexdigest().encode()
expected = hmac.new(key, raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header[len("sha256="):])
@app.post("/hooks/coldleads")
def coldleads_callback():
raw = request.get_data() # the raw bytes, before any JSON parsing
if not valid_signature(raw, request.headers.get("X-Coldleads-Signature"), CLAIM_TOKEN):
return "", 401
event = json.loads(raw)
if event.get("event") == "coldleads.account.activated":
# the callback carries no key: collect it with GET /api/agent/status + X-Claim-Token
print("owner paid, session", event["session_id"])
return "", 204Hinweise zur Sicherheit
- Behandeln Sie claim_token wie ein Passwort. Es wird nur einmal zurückgegeben, nur sein Hash wird gespeichert, und es ist der einzige Weg, den Schlüssel abzuholen: Ohne es enthält keine Statusantwort den Schlüssel, egal ob die Abfrage über session_id oder owner_email läuft.
- Der API-Schlüssel wird genau einmal ausgeliefert. Geht er verloren, meldet sich der Inhaber an und erneuert ihn unter Einstellungen → API-Schlüssel.
- Eine Zahlung beweist nicht, dass die zahlende Person das Postfach des Inhabers kontrolliert. Webzugang zum Konto erhält nur owner_email: über den einmaligen Link, der an diese Adresse gemailt wird, oder durch Anmeldung bei Cold Leads mit dieser Adresse, sobald sie verifiziert ist.
- Prüfen Sie die Callback-Signatur über den rohen Body, bevor Sie darauf reagieren, auch wenn der Callback keine Geheimnisse enthält.
- Unbezahlte Anfragen bleiben nicht liegen: Der Link läuft nach 24 Stunden ab, und unbezahlte Provisioning-Datensätze werden nach 7 Tagen zusammen mit ihrem ungenutzten Konto und Schlüssel gelöscht.
Limits und Kosten
- Tarif: Business mit API-Zugang, $99 pro Monat in US-Dollar, monatliche Verlängerung bis zur Kündigung; 10.000 Prüf- und API-Credits im Monat; zusätzliche Pakete zu 1.000 Credits kosten $5. Es können Steuern anfallen.
- Provisioning: 10 Anfragen pro Stunde und IP-Adresse und 3 Anfragen pro Tag und E-Mail-Adresse des Inhabers. Statusanfragen: 120 pro Minute und IP-Adresse.
- Nach der Aktivierung gilt für den Schlüssel das normale API-Limit von 120 Anfragen pro Minute.
FAQ
Kann der Agent bezahlen oder einen günstigeren Tarif wählen?
Nein. Der Link gilt für den Business-Tarif, den einzigen Tarif mit API, und nur der Mensch kann ihn auf der Stripe-Seite bezahlen. Die Aufgabe des Agenten ist, den Link zu zeigen und zu warten.
Was, wenn der Inhaber nie bezahlt?
Der Checkout-Link läuft nach 24 Stunden ab und der Status wird zu expired. Der Agent darf einen neuen Link anfordern (bis zu 3 Anfragen pro Tag und E-Mail-Adresse des Inhabers). Unbezahlte Datensätze werden nach 7 Tagen gelöscht.
Was, wenn der Inhaber Cold Leads schon nutzt?
Die Anfrage schlägt mit 409 account_exists fehl. Der Inhaber erstellt unter Einstellungen → API-Schlüssel einen geheimen Schlüssel (Business-Tarif) und übergibt ihn dem Agenten als COLDLEADS_API_KEY.
Geht das auch über MCP?
Ja, mit dem lokalen stdio-Server (npx -y --allow-git=root github:anttka4cz/mcp-server-coldleads). Er startet ohne Schlüssel, bietet provision_account_and_get_payment_link und check_provisioning_status und wechselt auf den neuen Schlüssel, sobald dieser abgeholt ist. Der gehostete Endpunkt unter coldleads.app/api/mcp verlangt für jede Anfrage einen Schlüssel.