Live: Tor + abuse feeds refreshed every 6 hours

LayerCall

Bubble

No-code

What exists today

Bubble's API Connector handles this with no plugin. The part that trips everyone up is initialisation: Bubble learns the response shape from one sample call, and any field missing from that sample does not exist for the rest of your app.

Setup

  1. 1Install the API Connector plugin (it ships with Bubble) and add a new API named LayerCall.
  2. 2Add a shared header X-Api-Key with your key, set to Private so it stays server-side.
  3. 3Add a call: POST to https://www.layercall.com/v1/score/user, body type JSON, using the body below.
  4. 4Mark ip, email and phone as parameters, untick Private on those so workflows can set them.
  5. 5Initialise the call with values that return a full response — see the pitfall — then use it as an action in a workflow.

Code

{
  "ip": "<ip>",
  "email": "<email>",
  "phone": "<phone>"
}

What to do with each verdict

Three outcomes, and the middle one is the one worth getting right — refusing a real customer usually costs more than reviewing them.

VerdictIn Bubble
allowContinue the workflow — create the user, sign them up, whatever came next.
reviewSet a risk field on the User thing and continue. Use a Only when condition on later actions rather than a separate branch, which Bubble makes awkward.
blockAdd a Terminate this workflow action with a Only when condition of Result of step 1's verdict is block, and show an alert carrying the summary.

What comes back

A real response, generated from the live API rather than written by hand. Branch on verdict; summary is a sentence written to be shown to a person, and components_checked tells you what actually went into the score.

Show the full response(POST /v1/score/user)
{
  "risk_score": 65,
  "verdict": "review",
  "summary": "Needs review (65/100) — Tor exit node, commercial VPN and datacenter ASN.",
  "components": {
    "email": {
      "email": "test@guerrillamail.com",
      "normalized_email": "test@guerrillamail.com",
      "risk_score": 100,
      "verdict": "block",
      "status": "do_not_mail",
      "sub_status": "disposable",
      "deliverability_score": 0,
      "did_you_mean": null,
      "signals": {
        "syntax_valid": true,
        "mx_found": true,
        "is_disposable": true,
        "is_homograph": false,
        "is_role_account": true,
        "is_free_provider": false,
        "is_suspicious_handle": true,
        "is_tagged": false,
        "is_risky_tld": false,
        "is_new_domain": null,
        "has_spf": true,
        "has_dmarc": true,
        "has_website": true,
        "mailbox_exists": null,
        "is_catch_all": null,
        "mailbox_status": "unavailable",
        "has_digital_footprint": null
      },
      "domain": "guerrillamail.com",
      "domain_age_days": null,
      "mx_provider": null,
      "mx_records": [
        "mail.guerrillamail.com."
      ],
      "abuse_reports": 0,
      "digital_footprint": {
        "has_gravatar": false,
        "gravatar_profile_url": null,
        "breach_count": null,
        "seen_in_breach": null
      }
    },
    "phone": {
      "parse_status": "ok",
      "phone": "+14155552671",
      "risk_score": 0,
      "signals": {
        "syntax_valid": true,
        "is_possible": true,
        "is_voip": false,
        "is_premium_rate": false,
        "is_toll_free": false,
        "assigned_area_code": true,
        "is_fictional": false
      },
      "number": {
        "e164": "+14155552671",
        "country": "US",
        "national": "(415) 555-2671",
        "international": "+1 415 555 2671",
        "line_type": "fixed_line_or_mobile"
      },
      "verdict": "allow",
      "abuse_reports": 0
    },
    "ip": {
      "ip": "185.220.101.1",
      "risk_score": 60,
      "signals": {
        "is_vpn": true,
        "is_proxy": true,
        "is_datacenter": true,
        "is_tor": true,
        "recent_abuse": false,
        "is_hijacked_netblock": false
      },
      "geo": {
        "country": "DE",
        "city": "Berlin",
        "asn": "AS60729",
        "isp": "Stiftung Erneuerbare Freiheit"
      },
      "vpn_provider": null,
      "hijacked_source": null,
      "verdict": "review",
      "abuse_reports": 0
    }
  },
  "components_checked": [
    "email",
    "phone",
    "ip"
  ],
  "linkage": {
    "device_email_count": null,
    "email_device_count": 0,
    "email_ip_count": 0,
    "subnet_rate_1h": 2,
    "domain_rate_1h": 0
  },
  "actor": {
    "type": "unknown",
    "proven": false,
    "basis": "none",
    "operator": null,
    "trigger": null,
    "detail": "No signature and no device fingerprint. Drop fp.js on the page, or pass the agent's signed request, to get an answer here."
  },
  "top_signals": [
    "ip: tor exit node",
    "ip: commercial vpn",
    "ip: datacenter asn",
    "email: disposable domain",
    "email: role account",
    "email: machine-generated handle"
  ]
}

Traps specific to Bubble

Initialising with a partial call permanently hides fields

Bubble builds the data type from the initialisation response. Initialise with only an email and the ip fields never appear in the expression composer — and re-initialising later can break every expression already built on the old shape. Initialise once, with ip, email AND phone all populated.

Untick Private on parameters, keep it ticked on the key

Private means Bubble fills the value server-side and never exposes it. That is right for the API key and wrong for ip/email/phone, which your workflow needs to set. Getting it backwards either leaks the key or leaves you unable to pass data.

Bubble's client IP is not automatic

There is no built-in 'current user's IP' in Bubble. You need a plugin that exposes it, or you pass it from a page-load workflow. Sending an empty ip is not an error — it just scores as unknown, so the call looks like it worked while measuring nothing.

Questions

Does this need a paid Bubble plan?

The API Connector works on all plans including free. Server-side workflows and scheduled workflows have plan limits, but a single API call from a page workflow does not.

Action or data source?

Action. Set it to 'Use as Action' so it runs once when the workflow reaches it. As a data source Bubble may call it repeatedly while re-rendering, and each call is a billed lookup.