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.
- 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.
When to use it
Section titled “When to use it”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.
What you'll build
Section titled “What you'll build”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 SessionNo 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.
Configure it
Section titled “Configure it”1. Get a test webhook URL
Section titled “1. Get a test webhook URL”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.
Anyone with the inspector URL can read what your Flow posts there. Use a tenant test user and fake email when developing against webhook.site or beeceptor.com; switch to your real backend URL before going live. Treat the inspector URL itself as semi-sensitive — anyone you share it with can see the requests too.
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.
2. Open the Login flow
Section titled “2. Open the Login flow”In the admin console sidebar, click Flows, then click Login. Click Edit to open the flow editor.
3. Add the Call Webhook block
Section titled “3. Add the Call Webhook block”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 asstep.fraud-signal.*. - URL — paste your webhook.site URL (or your fraud partner's production endpoint).
- Method —
POST. - Headers — add two entries:
Content-Type:application/jsonX-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.
The template references event.user.email, which is populated only after an Identity Lookup block has run. In the default Login flow, Identity Lookup and Password Check are in the Pre-Login stage, so they run before Post-Auth. If you have a custom flow layout, confirm Identity Lookup is upstream.
4. Add the Decision block
Section titled “4. Add the Decision block”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 name | Condition | Route to |
|---|---|---|
deny | step.fraud-signal.body.risk_score > 0.7 | Deny block |
step-up | step.fraud-signal.body.risk_score > 0.4 | MFA 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.
5. Wire the branches
Section titled “5. Wire the branches”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.
6. Save the flow
Section titled “6. Save the flow”Click Save flow. The editor validates all branches and checks that every path reaches a terminal block before it saves.
Test it
Section titled “Test it”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.9Click 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.
Verify on a live flow
Section titled “Verify on a live flow”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.
Because webhook.site echoes requests but doesn't return a scored response, step.fraud-signal.body.risk_score will be undefined in a live test run. The Decision block treats undefined as falsy, so both threshold conditions evaluate to false and the flow takes the default (proceed) branch. That is the expected outcome when testing with a passive inspector rather than a mock responder.
Cautions
Section titled “Cautions”- 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.emailare 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_codeunset. 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 todeny. 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.