Tunnels & webhooks — for you and your agents

Webhooks that never sleep.
Tunnels in one command.

Permanent endpoints that answer, verify, and forward webhooks around the clock — and instant HTTPS tunnels to any local port. Point and click in the console, or hand your agents the CLI and built-in MCP server.

$npx otterkit webhook$npx otterkit tunnel 3000
FROM THE INTERNETBrowserStripeGitHubSlackWebhook endpointhook-live.otterkit.app✓ verified · answered 200always on — even offlineInstant tunneldemo.otterkit.app→ 127.0.0.1:3000 on your machine200 GET / · 11msYOUR AGENT, DRIVINGagent@otterkit — zsh$

One URL, five jobs

What happens when a request lands.

Answered

Your status, headers, body, and delay — or a response rule or script decides per request.

Verified

Signature checked in constant time against 16 provider presets, marked in the feed.

Captured

Full request stored — browse, filter, and export the history as JSON or CSV.

Forwarded

Matching events delivered to any URL — signed, transformed, retried until they land.

Notified

An email or browser ping when the request you are waiting for arrives.

Webhook endpoints

An endpoint with no server behind it.

Provision a URL once and it's simply on: OtterKit answers every sender 24/7, keeps the full history, and buffers nothing on your machine — because nothing runs on your machine. Come back whenever; replay any capture with edited fields, or export the lot.

--ttl neveralways-on, capped at $2/mo--respond 201 --respond-delay 800shape the answerreplay --set amount=42edit & resend capturesrequests · export json/csvhistory anywhere

$ npx otterkit webhook --daemon --subdomain stripe-live

Endpoint live https://stripe-live.otterkit.app

answering 200 · storing history · runs until you stop it

# three days later, from anywhere

$ npx otterkit requests stripe-live --last 2

09:14 POST /stripe 200 · 1.2 KB · verified ✓

11:02 POST /stripe 200 · 1.3 KB · verified ✓

Signature verification

Know it's really Stripe before you trust it.

Every capture is checked against the provider's signing scheme as it lands — constant-time, spoof-proof, marked right in the feed. Gate your forwards on it and a forged call can never reach your CI. Sixteen provider presets, public-key schemes included, plus generic HMAC for everything else.

--verify stripe:whsec_…arm at provision time--verified-onlyforwards fail closedsend stripe:payment.succeededcorrectly-signed test events
StripeGitHubShopifySlackSvix · Resend · ClerkPaddleZoomLinearLemon SqueezyDropboxGitLabTwilioSquareHubSpotDiscord · Ed25519SendGrid · ECDSA+ generic HMAC

Dynamic responses

Answer like the API you're pretending to be.

Response rules run first-match-wins: match on method, path, or a JSON field, and answer with a static body or a template that echoes pieces of the request. The Slack challenge, a fake 500, a slow consumer — all declarative, edited live on the console, applied without a restart.

--respond-match type=url_verificationSlack challenge, solved{{$body}}template echoes--respond 500simulate failure--respond-delay 800simulate a slow consumer
Response rules · first match wins
type=url_verification200{"challenge":"{{…}}"}template
POST /fail500{"error":"simulated_failure"}static
everything else200{"received":true}default

Scriptable, sandboxed

When rules run out, write JavaScript.

Both ends of an endpoint take a script. respond() decides the answer to each request; transform() reshapes a forward on its way out. They run in an isolated sandbox with no network access, TypeScript works out of the box, and a script that throws fails open — your URL keeps answering no matter what.

respond.ts--respond-script

export default {

respond(req) {

// Exercise the sender’s retry logic ~30% of the time.

if (Math.random() < 0.3)

return { status: 500, body: '{"error":"chaos"}' };

return null; // fall through to the next rule

},

};

transform.ts--forward-script

export default {

transform(req) {

const event = JSON.parse(req.bodyText);

// Forward only what your CI actually wants.

return {

body: JSON.stringify({ run: event.data.id, paid: true }),

headers: { 'X-Event-Type': event.type },

};

},

};

no-egress isolatescripts can't phone homefail-opena throw never takes your URL downTypeScripttypes stripped on savechaos presetretry-testing in one click

Durable forwards

The event that matters finds you. Eventually is guaranteed.

Attach a rule to the endpoint: every request matching a method, path, or JSON field is delivered to your URL — HMAC-signed, reshaped by a template or script if you want, retried until it lands, paused automatically if the target dies. It fires whether or not any machine of yours is awake.

--match event.type=paidmethod · path · JSON field--forward-scriptreshape the payload en routeX-OtterKit-Signaturesigned + retried--notify-matchemail on the ones you care about

$ npx otterkit webhook --match event.type=paid \

--forward https://ci.example.com/resume

Forwarding armed · every match delivered, signed

# hours later — laptop closed, payment lands

delivered → ci.example.com (2 retries, 4m total)

Tunnels

When you want it on your machine, it's one command.

Expose any local port on *.otterkit.app with HTTPS — a stable subdomain that survives restarts, a TTL so forgotten tunnels stop themselves, daemon mode for the background, and an otterkit.toml that brings a whole project up at once. Inspect everything, replay anything.

--subdomain api-demo --ttl 4hstable + self-stoppingotterkit upwhole project from toml--auth user:passbasic-auth the doorinspect --haropen in devtools

$ npx otterkit tunnel 3000 --subdomain demo --ttl 4h

Tunnel ready https://demo.otterkit.app

→ 127.0.0.1:3000 · auto-stops in 4h · 1¢/hr

200 GET / (11ms)

201 POST /api/orders (18ms)

Built agent-first

Your agents don't ask you for URLs anymore.

Log in once. From then on, any agent on the machine provisions endpoints and tunnels itself against your prepaid credits — no keys to paste, no dashboard round-trips. Every command speaks --json, await blocks until the webhook a test is waiting for actually arrives, and the MCP server plugs into Claude Code or Cursor with one line.

npx otterkit mcp
  • Agents provision, capture, await, verify, and replay on their own
  • Server-held forwards instead of polling — nothing runs locally to miss the event
  • llms.txt and an agent skill, so the docs read themselves

# an agent, testing its own payment flow

$ otterkit webhook --daemon --json

{"url":"https://hook-1f2e3d4c.otterkit.app", …}

$ otterkit await hook-1f2e3d4c --path /stripe --count 1 --timeout 5m

waiting for a matching request…

match POST /stripe · verified ✓ · exit 0

Pricing

Fair, and honest about it.

No subscription — prepaid credits, metered by the connected hour, and webhooks are capped so an always-on endpoint can never surprise you.

CLI + console · for you and your agents

/ connected hour

Free credits to start · no card required

  • Webhook endpoints never bill more than $2 / month — even always-on
  • Tunnels bill connected time only
  • Credits never expire · log in once, agents self-provision
  • Verification, forwards, scripts, notifications — all included
Get started free

FAQ

Questions, answered.

Two things. Webhook endpoints: permanent URLs served by OtterKit that answer senders, verify signatures, store history, and forward matching events — no server of yours involved. Tunnels: public HTTPS URLs for any local port, one command. Drive it from the console, or hand your agents the CLI and MCP server.

Put a URL on it.

Your first endpoint is one command away, and the free credits cover it. Bring an agent — or don't.

$ npx otterkit webhook