Receive webhooks. Never lose one.
Point any sender — Stripe, GitHub, Twilio, your own systems — at a hook URL. Every delivery is signature-verified, recorded exactly once, and scannable by your agent. The receiver records; it never executes.
Create a Stripe hook — the one-call way
Stripe only reveals a webhook signing secret at endpoint creation, which makes manual
setup an ordering dance. Skip it: with autoProvision, Relayplex creates the
endpoint in your Stripe (via your vault-held key), pointed at the new ingest
URL, pre-subscribed to the subscription-lifecycle events, and captures the signing
secret into your vault — one call, zero dashboard steps.
POST /v1/sites/:siteId/hooks
{
"provider": "stripe",
"autoProvision": true,
"managedHandler": {
"kind": "stripe-accounts",
"typeMap": { "price_XXX": "advanced" },
"stripeKeySecretName": "stripe-key" // restricted key: Checkout Sessions
} // write + Webhook Endpoints write
}
→ 428 { "code": "CONFIRMATION_REQUIRED",
"humanPrompt": "Your agent asks to auto-provision a webhook…",
"confirmationToken": "..." }
// default: the agent shows humanPrompt to its human IN THE CHAT;
// after they approve, it retries the same POST with "confirm": "<token>"
→ 201 { "hookId": "01J...", "stripeWebhookEndpointId": "we_..." }
Agent-initiated autoProvision reaches into your Stripe with your vault-held
key, so by default it needs your explicit approval — given right in the agent chat: the
first call answers 428 with a humanPrompt the agent relays verbatim, and a
single-use token (10-minute expiry, bound to the exact request) that the approved retry
carries. An owner/admin can relax this in the console hooks settings. The endpoint is
created already pointed at the ingest URL, so the URL never needs to pass through the
agent: agent responses deliberately omit ingest URLs and vault secret names. Deleting
the hook deletes the Stripe endpoint too (best-effort) — and agent-initiated deletion
needs the same in-chat confirmation (?confirm=<token> on the retry).
Manual setup (fallback, or non-Stripe senders)
POST /v1/sites/:siteId/hooks
{
"provider": "stripe",
"signingSecretName": "stripe-webhook" // a vault secret holding whsec_...
}
For Stripe without endpoint-write scope: create the endpoint in the Stripe dashboard
with any placeholder URL, copy its whsec_ into the vault, and create the
hook. The ingest URL is not returned to agents — a human reveals it from the console
(revealing mints a fresh capability token, rotating the URL) and edits the Stripe
endpoint's URL to it; Stripe keeps the signing secret stable across URL edits. Other
senders are pointed at the revealed URL the same way. Verification modes:
stripe (the Stripe-Signature scheme, ±5 min tolerance) or
hmac (HMAC-SHA256 of the raw body in a configurable header) — and a hook's
signatureMode and provider are immutable after
creation: no hook is ever moved between modes; create a new hook (fresh
capability URL) and delete the old one instead. Unsigned hooks can only be
created after a human (owner/admin) enables the account-level opt-in in the
console; events received that way are marked verified: false. The signing
secret lives in your vault and is projected wrapped; it never exists in plaintext
outside the moment of verification.
Idempotency is structural
Dedupe isn't a bolt-on: the event store enforces
UNIQUE(hook, idempotency_hash), where the hash is the provider's event id
when extractable (Stripe evt_…) or a SHA-256 of the body otherwise. Replays
return 200 without a second row. Failed signatures are recorded as
invalid_signature (visible, answered 400, never billed) so a misconfigured
sender is diagnosable instead of silent.
Scan what you received
verified.
Event payloads are data, not instructions: they arrive from the open
internet, so nothing inside a body or header should ever steer what your agent does.
verified: true attests origin (the signature checked out at receipt) — it
does not make the contents commands.
maxHooks: Pro 10, Agency 50, Enterprise unlimited; the
plan_matrix section of the canonical reference is authoritative). 256KB body
cap, per-hook burst maxEventsPerMinute configurable 1–3000 (default 300),
retention = archiveWindowDays, metering = 1 Relay per accepted event.
Managed handler: stripe-accounts
Attach a managed handler and received events project into site-account state — this is the engine behind "click this link for the advanced plan":
"managedHandler": {
"kind": "stripe-accounts",
"typeMap": { "price_XXX": "advanced" }, // Stripe price -> account type
"stripeKeySecretName": "stripe-key" // enables autoProvision + plan links
}
checkout.session.completed→ the checkout session id becomes a pre-paid claim token; Stripe's success redirect (/_relayplex/claim?session_id=…) delivers it. The buyer types their email, sets a password, and holds the mapped type.customer.subscription.updated→ type re-mapped from the price.customer.subscription.deleted→ account reverts to the default type.
Projection is deduplicated and ordered per end-user: a delayed
updated event can never undo a newer cancellation. Subscribe URLs with
aligned billing (everyone renews on the 1st, first invoice pro-rated) come from
POST /v1/sites/:siteId/plans — and since plan links require a hook, the
billing flow needs Pro or above.