Skip to main content

Documentation Index

Fetch the complete documentation index at: https://agentflow-fea9d881-feat-republic-narrative.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

AgentFlow can POST to a URL of your choice whenever an event happens on your account. Subscribe via the Cabinet (Settings → Webhooks) or via API.

Manage subscriptions

# Create
curl -X POST https://api.agentflow.website/me/webhooks \
  -H "x-api-key: af_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example/agentflow",
    "events": ["payment.received", "payout.sent", "subscription.granted"]
  }'

# List
curl https://api.agentflow.website/me/webhooks -H "x-api-key: af_live_…"

# Test ping
curl -X POST https://api.agentflow.website/me/webhooks/123/test -H "x-api-key: af_live_…"

# Delete
curl -X DELETE https://api.agentflow.website/me/webhooks/123 -H "x-api-key: af_live_…"
Empty events: [] means subscribe to all future event types.

Known events

GET /me/webhooks/events returns the canonical list. As of today:
EventPayload highlight
project.completedslug, artifact_url
project.failedslug, reason
payment.receivedprovider, amount_usd, amount_flow
subscription.grantedtier, amount_flow
agent.publishedslug, marketplace_meta
flow.balance.lowbalance, threshold
payout.sentpayout_id, amount_flow, tx_hash
payout.failedpayout_id, amount_flow, reason

Envelope

Every delivery is JSON:
{
  "id": "01996e6f-…",
  "event": "payment.received",
  "user_id": 42,
  "created_at": "2026-04-25T12:00:00.000Z",
  "data": { "provider": "cryptobot", "amount_flow": "100.000000" }
}
Use id to dedupe retries — POST is at-least-once.

Signing & verification

Each delivery sends X-AgentFlow-Signature: t=<unix>,v1=<hex>. The signed string is <unix>.<raw-body-json> (Stripe-style). Reject any request whose signature doesn’t match and any request whose timestamp is more than 5 minutes off now.
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(
    header.split(",").map((p) => {
      const i = p.indexOf("=");
      return [p.slice(0, i), p.slice(i + 1)];
    }),
  ) as Record<string, string>;
  const t = Number(parts.t);
  const v1 = parts.v1;
  if (!t || !v1) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(v1, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, v1 = int(parts.get("t", 0)), parts.get("v1", "")
    if not t or not v1: return False
    if abs(int(time.time()) - t) > 300: return False
    expected = hmac.new(
        secret.encode(),
        f"{t}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, v1)

Retries

Failed deliveries (non-2xx, network error) are retried up to 3 times with full-jitter exponential backoff (~1s, ~2s, ~4s). After the final failure we persist last_error on the row — the Cabinet surfaces it.

SDK

import { AgentFlow } from "@agentflow/sdk";
const af = new AgentFlow({ apiKey: process.env.AF_KEY });

// Discover what's subscribable
const { events } = await af.webhooks.events();

// Create
const { webhook } = await af.webhooks.create({
  url: "https://your-app.example/agentflow",
  events: ["payment.received", "payout.sent"],
});
console.log("Save this secret:", webhook.secret);

// Test
await af.webhooks.test(webhook.id);
Each subscription has its own secret. Treat it like a password — leak only one and the others stay safe.