Zapier
There is no LayerCall app in the Zapier directory yet. You do not need one: Zapier's built-in Webhooks by Zapier action calls any HTTP API, and that is how this works today.
Setup
- 1Add a Webhooks by Zapier action and choose 'POST'.
- 2Set the URL to https://www.layercall.com/v1/score/user
- 3Under Data, add the fields you have: ip, email, phone.
- 4Under Headers, add X-Api-Key with your API key, and Content-Type as application/json.
- 5Add a Filter step after it so the Zap only continues when verdict is not 'block'.
Code
// Code by Zapier → Run JavaScript. // Map ip / email / phone into "Input Data" in the step UI first. // // Use this instead of Webhooks + Filter when you want the branching and the // call in one task — Zapier bills per task, so two steps cost two tasks. const res = await fetch("https://www.layercall.com/v1/score/user", { method: "POST", headers: { "X-Api-Key": inputData.apiKey, "Content-Type": "application/json", }, body: JSON.stringify({ ip: inputData.ip, email: inputData.email, phone: inputData.phone, }), }); if (!res.ok) { // Fail open. A Zap that halts on our outage stops your signups too. output = { verdict: "allow", risk_score: 0, degraded: true }; } else { const data = await res.json(); output = { verdict: data.verdict, risk_score: data.risk_score, summary: data.summary, top_signals: (data.top_signals || []).join(", "), }; }
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.
| Verdict | In Zapier |
|---|---|
| allow | Let the Zap continue to the next step as normal. |
| review | Branch to a Slack or email notification carrying data.summary, and hold the record in a 'needs review' state rather than rejecting it. |
| block | Stop the Zap with a Filter, or route to a suppression list. Do not delete the record — you will want it when someone appeals. |
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 Zapier
Zapier has no secret store for Code steps or webhook headers — whatever you type is shown in plain text to every user with edit access to that Zap. On a shared Zapier account, treat that key as shared, and rotate it when someone leaves.
It needs a paid Zapier plan. That is Zapier's restriction and nothing about the API requires it — Code by Zapier is available on more plans and can make the same call.
Filtering after a webhook means two tasks per record. Doing the call and the decision inside one Code step halves that.
Questions
Which Zapier plan do I need?
Webhooks by Zapier is a premium action, so it needs a paid Zapier plan. That is Zapier's restriction, not ours — nothing about our API requires it. Code by Zapier reaches the same endpoint and is available more widely.
How many tasks does this use?
One Zapier task per call, plus one LayerCall lookup per value scored. A unified call checking ip, email and phone is one Zapier task and three lookups.
What happens if LayerCall is down mid-Zap?
The fetch throws or returns a non-2xx and, with the snippet above, the step outputs verdict 'allow' with degraded: true. That is deliberate — a fraud check that halts your signup flow during an outage costs more than the fraud it was catching. Filter on degraded if you want those records reviewed later.