Skip to content

Recipe — Fraud webhook branch

Add an external fraud signal to your Login flow without writing a line of JavaScript. When the login context reaches your fraud partner, they return a score; a Decision block reads it and routes the session to allow, step-up MFA, or deny — all in the same flow run.

Before you begin
  • You are signed in as a tenant admin with permission to edit flows.
  • You have read Flows concepts — the Flow / Stage / Block vocabulary should be familiar.
  • Your fraud partner exposes an HTTPS endpoint that accepts a JSON POST and returns a JSON body containing a numeric score field. For development you can substitute webhook.site.
  • The partner's domain is on your tenant's outbound URL allowlist (Security settings). If it isn't, the Call Webhook block will fail before any request leaves the platform.

Call a fraud-scoring partner during every login, capture the numeric risk score it returns, and branch the flow: low score proceeds to a session, medium score challenges the user with MFA, high score blocks the login outright.

Reach for this recipe when your product already has a fraud or bot-detection partner — Sift, Castle, Stripe Radar, or a homegrown scoring service — and you want to use that signal inside the login flow without writing custom action code. If your only signal is the platform's own built-in risk engine, use Risk Evaluate alone and skip the external call.

A Login flow with a Post-Auth stage containing three blocks in sequence:

Post-Auth stage
├─ Block: Call Webhook (slug: fraud-signal) → POST login context to partner
├─ Block: Decision → branch on step.fraud-signal.body.risk_score
│ ├─ branch deny → step.fraud-signal.body.risk_score > 0.7
│ ├─ branch step-up → step.fraud-signal.body.risk_score > 0.4
│ └─ branch proceed → (default)
├─ branch deny: Deny block
├─ branch step-up: MFA → Issue Session
└─ branch proceed: Issue Session

No custom action is needed. The Call Webhook block handles the outbound HTTP call and parses the response; the Decision block reads the parsed field by name.

Open webhook.site in a new tab. Copy the unique URL it generates for you — it looks like https://webhook.site/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Keep the tab open; every request the platform makes will appear there in real time.

If you want the Decision block to branch on a real score during development, use beeceptor.com instead — it lets you define a mock response body. Create an endpoint, add a rule that returns {"risk_score": 0.85} for every POST, and copy that URL. The flow will read the mocked score and route accordingly.

In the admin console sidebar, click Flows, then click Login. Click Edit to open the flow editor.

Locate the Post-Auth stage. Drag a Call Webhook block into that stage, placing it after any existing Risk Evaluate block and before Issue Session.

Click the block to open its config panel. Fill in the fields:

  • Label — type fraud-signal. The platform uses this label as the block's slug, so step outputs are accessible as step.fraud-signal.*.
  • URL — paste your webhook.site URL (or your fraud partner's production endpoint).
  • MethodPOST.
  • Headers — add two entries:
    • Content-Type : application/json
    • X-Tenant : {{ event.tenant.slug }} (click the {} picker and select Tenant → slug — the platform substitutes the real value at runtime)
  • Body — paste the following JSON template:
{
"email": "{{ event.user.email }}",
"ip": "{{ event.request.ip }}",
"country": "{{ event.request.geo.country }}",
"visitor_id": "{{ event.request.visitor_id }}",
"is_vpn": "{{ event.request.asn.is_vpn }}"
}
  • Timeout — leave at the default 5 seconds. Your partner's endpoint should respond well within that window; if it doesn't, the block fails open and the flow continues.

Click Save block. The block appears in the stage with the slug fraud-signal shown beneath the label.

Drag a Decision block into the Post-Auth stage, directly below the Call Webhook block. Open its config panel.

Add three branches in this order:

Branch nameConditionRoute to
denystep.fraud-signal.body.risk_score > 0.7Deny block
step-upstep.fraud-signal.body.risk_score > 0.4MFA block
proceed(default — leave condition empty)Issue Session

The Decision block evaluates branches top to bottom and takes the first one whose condition is true. The default branch with no condition always matches, so it must be last.

Deny branch — drag a Deny block onto the deny branch path. In its config, set the reason to fraud-signal-high. This string appears in the audit log so you can identify which block triggered the rejection; it is not shown to the user.

Step-up branch — drag an MFA block onto the step-up path, followed by an Issue Session block.

Proceed branch — drag an Issue Session block directly onto the proceed path.

Click Save flow. The editor validates all branches and checks that every path reaches a terminal block before it saves.

The flow editor's Test pane lets you verify the Decision routing before touching a live login.

Open the Test pane (top right of the editor). Select Post-Auth as the starting stage. In the Step state override section, add a manual entry:

step.fraud-signal.body.risk_score = 0.9

Click Run test. The Decision block should evaluate the first branch (> 0.7), match, and route to the Deny terminal. The test output shows the branch taken and the terminal outcome.

Repeat with 0.55 — expect the step-up branch. Repeat with 0.2 — expect the proceed branch.

If you configured beeceptor with a mocked response, you can also run a full flow simulation. Select Simulate from the Test pane, choose the Login flow, and submit. The platform will call your beeceptor endpoint, parse the response, and route accordingly — all traceable in the Test output.

Publish the flow from the editor (click Publish). Then sign in to your tenant's user-facing URL using a test account.

Switch back to webhook.site. Within a few seconds you should see a POST request arrive with the body you configured — email, ip, country, visitor_id, is_vpn all substituted with the actual values from the login attempt.

In the admin console, return to Flows → Login → Recent Runs. Click the most recent run. The run trace shows each block, its inputs, its outputs, and the branch the Decision block took. Confirm step.fraud-signal.status_code shows 200 and step.fraud-signal.body.risk_score shows the value your partner (or mock) returned.

  • Use a passive inspector only to confirm the request shape. webhook.site tells you what the platform sent; it does not simulate your partner's response. To test the Decision branching paths, use beeceptor (mock response) or the Test pane with manual step-state overrides.
  • Outbound URLs must be HTTPS. The platform refuses HTTP and blocks requests to private addresses (RFC 1918 ranges, link-local, and cloud metadata endpoints) regardless of what you type in the URL field.
  • The template renders server-side with whatever is in state at that block. Fields like event.user.email are only available after Identity Lookup. If Identity Lookup is missing or has not yet run, those fields render as empty strings.
  • Webhook outages. The Call Webhook block's default on-failure policy is fail-open — the flow continues with step.fraud-signal.status_code unset. If you want the flow to fail closed on partner unavailability (deny the login when the score is unknown), set the block's On failure field to deny. Change this only after your partner's reliability and latency are well understood in production.
  • 5-second timeout. The platform aborts outbound calls that exceed 5 seconds. Design your partner endpoint to respond in under 3 seconds to leave headroom for network variance.
  • Score field name. This recipe references step.fraud-signal.body.risk_score. Your partner may use a different field name (score, fraud_score, risk, etc.). Check your partner's response schema and update the Decision block expressions accordingly.