Eventfy

Documentation

Everything Eventfy actually does, in the order you'll run into it — from your first catch URL to wiring test suites into CI.

Quickstart

There's no setup step before your first webhook. Sign up, and Eventfy creates a workspace and your first catch URL automatically.

  • Create an account at signup — enter your card details directly (processed securely via PayPal's Advanced Checkout, so your card details never touch our servers). A small verification charge confirms the card works; your 5-day trial itself is free, and nothing else is charged until it ends.
  • Your dashboard opens with one webhook URL already created and ready to receive events.
  • Copy that URL into whatever's sending the webhook — Stripe's dashboard, a GitHub repo's webhook settings, a Shopify app, or a script of your own.
  • Trigger the event. It shows up in your dashboard in under a second, with the payload, headers, and a plain-language summary of what happened.

The catch URL

Every webhook URL Eventfy gives you looks like https://yourapp.onrender.com/api/catch/<token>. It's a normal public HTTP endpoint that accepts POST requests — nothing provider-specific about it, which is why it works with literally anything that can fire a webhook.

Because it's hosted, not tunnelled from your machine, it keeps working when your laptop is closed, your dev server is down, or you're between projects. Whatever arrives while you're away is just sitting in your history when you're back.

Inspecting events

Each caught event shows: the full JSON body, every header, the detected platform, and a status derived from the payload itself (success, failed, or pending) — not just the HTTP code your server happened to return.

Eventfy fingerprints common providers automatically. A GitHub push event reads as "Push to main in you/repo — 3 commits by you", not just the raw event name. The same applies to pull requests, issues, releases, Stripe payment events, and Shopify order events.

Re-fire & edit payloads

Any caught event can be re-sent to a target of your choice — your local dev server, a staging URL, or back to itself. Before re-firing, you can edit the JSON body directly: change an amount, flip a status from succeeded to failed, remove a field your handler doesn't expect.

Re-fire sends a genuine outbound HTTP request from Eventfy's servers — not a browser-based replay — so it isn't subject to CORS, and it reaches localhost or an internal staging URL the same way the original webhook would have.

Workspaces

A workspace is a self-contained set of webhook URLs, event history, and settings. Each workspace has its own API key for CLI access.

Creating more than one workspace — to keep separate client projects, or a personal project and a work one, from mixing together — is available on Team (up to 5) and Business (up to 20). Individual and Pro are limited to a single workspace each.

Switching workspaces is instant from the sidebar and doesn't require re-authenticating; it just changes which workspace's data the rest of the dashboard reads.

The CLI — step by step

The CLI has two jobs: forwarding live events to your machine, and running test suites from a terminal or CI pipeline. There's no separate install step — it ships as a single Node script inside your project.

1. Get the CLI onto your machine

Install it once, globally. Zero dependencies, needs Node 18 or newer:

npm install -g eventfy-cli
eventfy --help

2. Grab your lens token

Open any webhook URL's Advanced panel in the dashboard — the token is the last part of the catch URL (https://yourapp.onrender.com/api/catch/a1b2c3d4e5), or copy the ready-made command shown right there, which already has it filled in.

3. Start forwarding

eventfy listen 3000 --server https://www.eventfy.space --lens <token>

Replace 3000 with whatever port your local server is actually running on, and the server URL with your own deployment. You should see something like:

Eventfy CLI v1.0.0
✓ connected to workspace "Jamie's Workspace"
✓ forwarding → localhost:3000
listening for events…

4. Confirm it's actually working

Trigger a test event from the provider's dashboard (Stripe has a "send test webhook" button; GitHub sends one automatically the moment you save a webhook — see the ping event note below), or just curl your own catch URL directly:

curl -X POST https://yourapp.onrender.com/api/catch/<token> \
  -H "Content-Type: application/json" \
  -d '{"test": true}'

If the CLI is connected correctly, that request appears in your terminal within about a second, and your local server on port 3000 receives a real POST / with that same body.

5. Common setup mistakes

Nothing arrives locally, but the dashboard shows the event. The CLI isn't running, isn't pointed at the right port, or your local server isn't actually listening on that port yet. Start the local server first, then the CLI.
"connection refused" / CLI won't connect. Double-check the --server URL — it should be your deployed Eventfy URL, not localhost. The token after --lens should match exactly what's shown in the dashboard, with no extra whitespace.
Works, then silently stops after a while. Some terminals or process managers suspend long-running scripts. Run it under pm2, a screen/tmux session, or a simple restart loop if you need it to survive longer than an active terminal session.

Use with an AI

Pro and above
Eventfy ships an MCP server, so an AI assistant (Claude Desktop, Claude Code, or anything else that speaks MCP) can create webhook URLs, read what they caught, re-fire events, and build flows for you — by asking in plain English.

Download it and point it at your workspace API key:

npm install -g eventfy-cli
eventfy-mcp --key <your-api-key>

In Claude Desktop, add it to claude_desktop_config.json:

{
  "mcpServers": {
    "eventfy": {
      "command": "eventfy-mcp",
      "args": ["--key", "<your-api-key>"]
    }
  }
}

What it can do

  • Webhook URLs — list, create, delete
  • Events — list what was caught, read a full payload, re-fire one at an endpoint
  • Flows — list, create with conditions and actions, dry-run against a real event, delete

So "make me a webhook for Stripe refunds and ping #alerts in Slack when one comes in over £100" is a single request rather than a form-filling session.

Every tool calls the same API the dashboard uses, with your workspace API key — so an AI can do exactly what a person holding that key could do, and no more. The key is scoped to one workspace: it cannot switch workspaces, read another workspace's data, or touch billing. Plan limits are still enforced server-side. Treat the key like a password, and if you ever need to cut access off, rotate it in the dashboard.

Dev / prod routing

A single catch URL can be configured to route to different targets depending on context — for example, always forward to your local machine while developing, and switch to a production target without changing the URL you gave the provider. This is set from the Advanced panel on each webhook URL.

Test suites — step by step

A test suite is a saved set of payloads — usually built from real events you've edited — that you can re-run on demand or wire into CI. Useful for checking a handler still behaves correctly against a Stripe decline, a malformed GitHub payload, or whatever edge case originally broke it, without waiting for that exact situation to happen again naturally.

1. Catch a real event to start from

Trigger the situation once for real (or as close as you can get) — a Stripe test payment, a GitHub pull request — so you have a genuine payload shape to work from rather than guessing at one.

2. Edit it into the edge case you actually want to test

Open the event in the dashboard and use re-fire's edit view to change whatever makes it the interesting case — flip payment_intent.succeeded to payment_intent.payment_failed, set an amount to zero, strip a field your handler currently assumes is always present.

3. Save it into a suite

From the event's re-fire panel, choose "Save as test suite step" (or add it to an existing suite). Repeat for as many edge cases as you want covered — a suite is just a named collection of these.

4. Run it manually

Open the suite from the dashboard and hit Run — it fires every saved payload at the target you choose (typically your local dev server via the CLI, or a staging URL) and reports pass/fail per step based on the HTTP status your handler returned.

5. Run it from CI

This needs an API key rather than a browser session, since CI has no cookie to authenticate with — grab it from the dashboard's Advanced panel (see API keys below), and store it as a CI secret, never in the workflow file itself.

eventfy run-suite <suite-id> --key <api-key>

Example as a GitHub Actions step, run after deploying to staging:

- name: Run webhook test suite
  run: eventfy run-suite ${{ vars.SUITE_ID }} --key ${{ secrets.EVENTFY_API_KEY }}
  env:
    EVENTFY_SERVER: https://yourapp.onrender.com

The command exits non-zero if any step fails, so it works as a normal CI gate — a failing webhook-handling regression blocks the build the same way a failing unit test would.

API keys

Each workspace has one API key, visible in the dashboard's Advanced panel. It authenticates CLI and CI requests via Authorization: Bearer <key>, scoped to that workspace only — it can't switch workspaces or see any other workspace's data.

Treat it like a password. Anyone with the key can create, read, and re-fire events in that workspace.

Slack / Discord alerts

This is a Pro feature and up.

On Pro and up, you can point a workspace at a Slack or Discord incoming webhook URL from Settings, and Eventfy will post there when an event comes in marked as failed — useful for catching a broken integration in production without watching the dashboard.

Flows — webhook automation

This is a Pro feature and up (5 active Flows on Pro, 25 on Team, 100 on Business).

A Flow watches one of your webhook URLs and, when a new event matches conditions you set, runs one or more actions automatically — no code, no server of your own required for the automation logic itself.

1. Pick a trigger

Every Flow is attached to exactly one webhook URL (lens). It runs the moment a new event lands there — there's no polling, no delay.

The fastest way to start: open Flows → + New flow and click one of the template buttons at the top (Stripe → Slack, Shopify → Slack, Webhook → Webhook). That fills in a working Flow you can then edit, rather than starting from an empty form.

2. Add conditions (optional)

Conditions filter which events actually trigger the Flow's actions. Leave them empty and every event on that URL matches. Each condition checks one field against a value:

  • status equals success — only successful events
  • payload.amount greater_than 5000 — reach into the actual JSON body, including nested fields
  • platform contains stripe — partial text match

Operators available: equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than, greater_than_or_equal, less_than_or_equal, exists, not_exists, is_empty, is_not_empty.

AND / OR

Conditions live in groups. Every condition inside a group must match (AND), and the Flow runs if any group matches (OR). So to build "a big payment or anything that failed":

  • Group 1: payload.amount greater_than 5000 — click + Add condition to add more rules to this same group.
  • Click + Add OR group, then in Group 2: status equals failed.

Up to 5 groups, up to 10 conditions in each. Older Flows saved before groups existed still work — they behave as a single AND group.

3. Add actions

Actions run in order once conditions pass, up to 5 per Flow. Three real action types exist today:

  • Forward to endpoint — sends the event to one of your saved targets, the same mechanism as manual re-fire.
  • Post to Slack — sends a message to the Slack webhook URL configured in Settings → Integrations for this workspace.
  • Post to Discord — same idea, for Discord.

Slack and Discord messages can pull values out of the event with {{field}} — the same field paths conditions use, so {{title}}: £{{payload.amount}} just came through works.

Reshaping the forwarded body (field mapping)

A Forward to endpoint action sends the original payload untouched by default. If the receiving service wants a different shape, fill in the optional custom JSON body box on that action:

{ "orderId": "{{payload.data.object.id}}",
  "amount": {{payload.data.object.amount}},
  "source": "eventfy" }

Each {{field}} is substituted, then the whole thing is parsed as JSON. Note the difference: "{{...}}" in quotes stays a string, while {{...}} without quotes becomes a number or boolean. If the result isn't valid JSON the action fails with that reason recorded in the run history rather than sending something malformed.

4. Test it before you trust it

Click on a Flow to run it against an event you've already caught, instead of waiting for live traffic. Pick one from the list and it runs immediately.

This is a real run, not a simulation — a Slack action really posts to Slack. The one difference is that test runs don't increment the Flow's "ran N times" counter, so that number stays a count of genuine production fires. The run still appears in history.

5. Execution history and retrying

Click on a Flow for its run history — every time the trigger fired, whether conditions matched, and the result of each action. This is what actually happened, not an estimate.

If a run has a failed action (the receiving service was down, say), a Retry failed button appears on it. That re-runs only the actions that failed, against that run's original event — the ones that already succeeded aren't repeated, so nobody gets a duplicate Slack message.

6. Duplicating and pausing

  • ⎘ Duplicate copies a Flow so you can tweak a variant. The copy always starts disabled, so experimenting can't double-fire your actions until you deliberately turn it on.
  • The toggle on each Flow card pauses it. A disabled Flow isn't even fetched when a webhook lands.
Not built: Google Sheets and Notion actions (both need a real OAuth app registered with that provider, which this deployment has no credentials for), true branching (different actions depending on which group matched — today all of a Flow's actions run together or not at all), and a data-transformation step beyond the JSON body template described above.

Your profile & account code

Everything here lives under Settings → Account.

Profile photo

Click Upload photo and pick a PNG, JPEG or WebP. The image is resized in your browser before it's uploaded, so a large photo won't upload a large file. It's shown to teammates you share a workspace with — on the members list and on invite previews. Remove clears it and falls back to your initial.

Name and title

Your name and an optional title ("Founder", "Backend Engineer") appear next to you in any shared workspace. Both are cosmetic — they don't affect permissions.

Account code

A code like EVF-K7M2Q-9XR4T, unique to your account, with a copy button next to it. Give it to someone who wants to add you to their workspace — see Team members. It's the only way anyone can look your account up, so treat it like a handle you choose to share rather than a secret you must protect: the worst someone can do with it is offer you an invite you can decline.

Changing your email or password

Both are on the same panel. To change your password you must enter your current one first, and the new one twice. Leave all three password boxes blank to change just your name, title or email.

Billing & payment

Under Settings → Billing you'll find two cards side by side.

The account card

Your plan, subscription status, name and reference ID. Its artwork changes with your plan — each tier has its own engraved design, so the card you saw at checkout is the one you keep seeing here.

The billing card

Email, payment method, next billing date (or trial end date), price, and how many webhook URLs you're using. Click Update payment method → and the card flips to a form where you can enter a new card.

Card details go directly into PayPal's own hosted fields — the raw number, expiry and CVV never reach Eventfy's servers. We only ever store the brand and last four digits, plus a reusable token held by PayPal.

Trials, renewals and cancelling

  • Every plan starts with a 5-day free trial. Your card is saved, not charged — nothing is billed until the trial ends.
  • When the trial ends, the plan price is charged automatically and renews each period after that.
  • Cancel at the bottom of the panel stops future renewals. You keep access until the end of the period you've already paid for, and a Resume button appears if you change your mind before then.

Plans & limits

Four plans, with real differences enforced by the server — not just marketing copy. Enterprise is a fifth, custom tier (contact sales) with no fixed price or self-serve checkout.

PlanWebhook URLsFlowsTest suitesTeam membersWorkspacesHistoryAlertsAIPrice
Individual53130 days£29/mo
Pro205Unlimited1180 days£49/mo
Team10025Unlimited551 year£149/mo
BusinessUnlimited100Unlimited20202 years£399/mo
"Team members" and "Workspaces" are two different, real things. Team members (Team/Business only) is how many separate people can be invited into and share one workspace's data — see Team members below. Workspaces is how many separate workspaces the owner's own account can create — every plan gets at least one, but creating more than one needs Team (5) or Business (20). Individual and Pro are single-workspace.

Everything else — the CLI, re-fire — works the same on every plan. Every plan includes a 5-day trial. Card details are entered directly at signup and processed securely through PayPal's Advanced Checkout (your card details never touch our servers). The card is saved, not charged — nothing at all is taken during the trial, and the first payment is the plan price when the 5 days end.

Prices above are in GBP. At signup you'll pick a billing country, and the price shown converts to your local currency automatically, with a 2.5% currency conversion fee disclosed separately — the same way a card network's own FX conversion works. The exact amount and currency are locked in at signup and don't change afterward even if exchange rates move.

Team members

This is a Team feature and up (5 seats on Team, 20 on Business — the workspace owner counts as one seat).

Invite people into your workspace and they see exactly what you see — the same webhook URLs, event history, and Flows, live.

Inviting someone

There are two ways, both from Settings → Members. Neither sends an email — Eventfy has no email delivery, so you copy the link and send it however you'd normally reach them.

By account code (recommended)

Every Eventfy account has a code that looks like EVF-K7M2Q-9XR4T, found under Settings → Account with a copy button. Ask the person for theirs, paste it into By account code, and hit Look up.

You'll see their actual profile — photo, name, title, email — so you can confirm you're adding the right person before sending anything. Pick their role and click Send invite.

An invite made this way is bound to that one account: if the link leaks or gets forwarded, nobody else can use it.

By shareable link

If you don't have their code, use Or a shareable link instead. Optionally add their email as a label for your own reference, pick a role, and create the link.

A plain link is a bearer token — anyone holding it can accept, and the email on it is just a label, not a verified restriction. Prefer the account-code route when you can, and revoke pending link invites you're unsure about.

Why you can't look someone up by email

Deliberate. If typing an email returned that person's name and photo, the invite box would become a way to test whether any address has an Eventfy account and see who's behind it. A code is a secret its owner chooses to hand out, so looking one up only ever reveals someone who already gave it to you.

Roles

  • Owner — you, automatically. Only the owner can invite, remove members, manage billing, rename, or delete the workspace.
  • Member — full access to webhook URLs, events, re-fire, test suites, and Flows. Can't manage members or billing.
  • Viewer — real read-only access, enforced by the server: can see everything (webhook URLs, events, Flows, test suites) but every create/edit/delete/re-fire action is blocked with a clear error, not just hidden in the UI.

Accepting an invite

The link works whether or not the person already has an Eventfy account — they'll be asked to log in or sign up first, then land back on the invite automatically to finish joining.

A code-bound invite only works for the account it was issued to — anyone else opening it is told it was meant for a different account.

Managing who has access

Settings → Members lists everyone with access as a card showing their photo, name, title and role, plus any pending invites. Remove someone with the 🗑 on their card; revoke an unaccepted invite the same way. Removing a member is immediate — their next request loses access to the workspace.

Supported providers

Any provider — Eventfy doesn't require you to select one. The catch URL accepts any POST request. A few platforms (GitHub, GitLab, Stripe, Shopify) get automatic fingerprinting so their events show up with human-readable summaries instead of raw JSON; every other provider still gets caught and inspected the same way, just without the provider-specific labeling.

What common webhook errors actually mean

The status codes and failure messages you'll see in each provider's own delivery log, decoded — this is about their retry/delivery behavior, not anything Eventfy-specific.

Stripe
Code / signalWhat it means
200Success — Stripe won't retry.
400Your handler explicitly rejected the event or threw an exception while processing it. Check your signature verification and event-handling logic first.
401 / 403Something is blocking the request before your webhook logic even runs — usually an auth middleware that isn't excluding the webhook route, or a failed Stripe-Signature check.
404The webhook URL doesn't exist at that path, or the route isn't registered on your server.
TimeoutYour endpoint took too long to respond. Stripe expects a fast 200 — do slow work asynchronously after responding, not before.
429Too many requests hit your endpoint too quickly — rare for webhooks specifically, more common on the API side.
Signature mismatchUsually one of: the wrong signing secret (each endpoint has its own whsec_...), a middleware that parsed and re-serialized the JSON body before your verification code read the raw bytes, or server clock skew (Stripe signs with a timestamp).
GitHub
Code / signalWhat it means
Ping eventSent automatically the moment you create or edit a webhook, with a random quote in a zen field. It's just a reachability check — always return 200 for it.
404GitHub reached your server but couldn't find that route — check the URL saved in the webhook's settings.
Signature check failsVerify against X-Hub-Signature-256 (HMAC-SHA256), not the older X-Hub-Signature (SHA-1) — GitHub sends both, but only SHA-256 is recommended. Common causes: hashing the parsed/re-serialized body instead of the exact raw bytes, or a secret that doesn't match what's configured in the repo's webhook settings.
Recent Deliveries tabUnder the repo's webhook settings — shows the exact status code and response body for every delivery attempt, and lets you redeliver one on demand. First stop for debugging.
PayPal
Code / signalWhat it means
VERIFICATION_STATUS: FAILUREReturned by PayPal's own /v1/notifications/verify-webhook-signature endpoint when a webhook's RSA-SHA256 signature doesn't check out — usually caused by re-serializing the JSON body before verifying, or using a Webhook ID from the wrong environment (sandbox vs live have different IDs).
Signature headers missingPayPal sends PAYPAL-TRANSMISSION-ID, PAYPAL-TRANSMISSION-TIME, PAYPAL-TRANSMISSION-SIG, PAYPAL-CERT-URL, and PAYPAL-AUTH-ALGO with every delivery — if any are absent, the request likely didn't come from PayPal at all.
Order status not COMPLETEDRelevant if you're verifying orders directly (as Eventfy's own PayPal signup flow does) rather than via webhooks — an order in CREATED or APPROVED hasn't actually been captured yet.
Vercel
Code / signalWhat it means
x-vercel-signature mismatchVercel signs the raw request body with SHA-1 using your webhook secret (account webhooks) or Integration/Client Secret (integration webhooks) — recompute and compare rather than trusting the payload unchecked.
deployment.errorA deployment event type, not an HTTP error — means the deployment itself failed to build, distinct from a webhook delivery problem.
Across every provider here, the same three root causes explain almost every "signature verification failed" report: a middleware re-parsing the body before your code sees the raw bytes, a secret that's drifted out of sync between the two systems, or checking the wrong header entirely.