Ropeline

For developers · any platform

Bot protection.
Any website.

Not on WordPress? Your site still gets card testing, fake signups, password guessing and form spam. Ask Ropeline before each login, signup, checkout or form post, and it tells you to allow, challenge or block it.

Node · PHP · Python · Ruby · Go · Cloudflare Workers · No card for the free week

network.getropeline.com/v1/checkExample
  1. login198.51.100.23Right password, first tryAllow
  2. checkout203.0.113.543rd declined card this hourChallenge
  3. signup192.0.2.77Throwaway email addressChallenge
  4. checkout203.0.113.545th declined card this hourBlock
  5. form198.51.100.8Contact form, first postAllow

Stop bots, not customers.

The call

One question before every door.

Your server asks before a login, signup, checkout or form post. Ropeline answers in plain words.

Three answers

Allow. Challenge. Block.

With the reasons, so you always know why.

Fails open

Never the reason a customer can't get in.

If Ropeline doesn't answer within 1.5 seconds, the code on this page lets the visitor through.

The Ropeline Network

Learns from every Ropeline site.

Bots that other sites stopped are challenged at yours before they try a single password.

No code

Behind Cloudflare? Paste one file.

A Worker in front of your site asks Ropeline for you. Your site's code doesn't change.

Private

No passwords. No card numbers.

Ropeline only needs the visitor's address and what they tried.

Bots don't check what your site is built on. They try every login, every signup and every checkout they can find. Ropeline now stands at all of them, on any website, in any language.

The answer

Know why every visitor
got in, or didn't.

Every answer comes with its reasons in plain words, the tries counted so far and the lines for that door.

Allow

Nothing unusual. Carry on as normal.

Challenge

Ask first: show a CAPTCHA, or send a code. Real people pass and carry on.

Block

Turn them away, and your site's report goes to the Ropeline Network's shared list.

How it decides

A few tries,
then the door closes.

From the same signals Ropeline Pro uses on WordPress: how often this address tried on your site in the last hour, the Ropeline Network's shared list, and throwaway email addresses on signups.

login

Logins

Counts failed logins

Challenge from 5Block from 10

signup

Signups

Counts signups

Challenge from 3Block from 6

checkout

Checkout

Counts declined payments

Challenge from 3Block from 5

form

Forms

Counts form posts

Challenge from 5Block from 10

Each per address, per website, in an hour. Failed logins and declined payments count when you tell Ropeline they failed; every signup and form post counts when you ask. An address on the shared list is challenged, and blocked once it also reaches the challenge line.

The Ropeline Network

One attack, every site protected.

The shared list holds addresses that sites on at least three different networks stopped in the last day. A bot that attacks one site is already known at the next. When an address reaches a block line on your site, Ropeline adds your site's report to the shared list, so your attackers are known everywhere else too.

The code

A few lines.
Any language.

Send your key in the X-Ropeline-Key header, from your server only: the key is a password, so never put it in a web page.

Terminal
curl https://network.getropeline.com/v1/check \
  -H "X-Ropeline-Key: RL-XXXXX-XXXXX-XXXXX-XXXXX" \
  -H "Content-Type: application/json" \
  -d '{"site": "example.com", "event": "login", "ip": "203.0.113.7"}'
Node, Deno, Bun · ropeline.js, about 40 lines, no dependencies
import { createRopeline } from "./ropeline.js";
const ropeline = createRopeline({ key: process.env.ROPELINE_KEY, site: "example.com" });

app.post("/login", async (req, res) => {
  const verdict = await ropeline.check("login", { ip: req.ip }); // behind a proxy: app.set("trust proxy", true)
  if (verdict.action === "block") return res.status(429).send("Too many tries. Please wait an hour.");
  if (verdict.action === "challenge" && !captchaPassed(req)) return showCaptcha(res);
  if (!(await passwordIsRight(req.body))) {
    await ropeline.failed("login", { ip: req.ip });
    return res.status(401).send("Wrong email or password.");
  }
  // ...log them in
});
PHP, with curl
function ropeline_check($event, $ip, $failed = false, $email = '') {
    $ch = curl_init('https://network.getropeline.com/v1/check');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT_MS => 1500,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Ropeline-Key: ' . getenv('ROPELINE_KEY')],
        CURLOPT_POSTFIELDS => json_encode(['site' => 'example.com', 'event' => $event, 'ip' => $ip, 'failed' => $failed, 'email' => $email]),
    ]);
    $answer = json_decode((string) curl_exec($ch), true);
    return is_array($answer) && isset($answer['action']) ? $answer['action'] : 'allow';
}

if (ropeline_check('signup', $_SERVER['REMOTE_ADDR'], false, $_POST['email']) === 'block') {
    http_response_code(429);
    exit('Too many signups from your connection. Please try again later.');
}
Python, with requests
import os, requests

def ropeline_check(event, ip, failed=False, email=""):
    try:
        r = requests.post("https://network.getropeline.com/v1/check", timeout=1.5,
                          headers={"X-Ropeline-Key": os.environ["ROPELINE_KEY"]},
                          json={"site": "example.com", "event": event, "ip": ip, "failed": failed, "email": email})
        return r.json().get("action", "allow")
    except requests.RequestException:
        return "allow"

# After a declined payment (card testing shows up as many of these from one address):
ropeline_check("checkout", request.remote_addr, failed=True)
The answer
{"action": "block", "reasons": ["10 failed logins from this address on this site in the last hour"],
 "listed": false, "tries": 10, "limits": {"challenge": 5, "block": 10}, "plan": "site"}

Trying it out? Send "site": "localhost" first: test copies of a site (localhost, and addresses ending in .local, .test or .localhost) never take one of your plan's places.

Reference

The call, in full.

POST https://network.getropeline.com/v1/check, with your key in the X-Ropeline-Key header and a JSON body.

site
Your website's address, like example.com (or example.com/shop for a site in a folder). Each website takes one of your plan's places (local copies such as localhost don't).
event
login, signup, checkout or form.
ip
The visitor's own address (IPv4, or IPv6, counted by its /64). Behind a proxy or load balancer, that's in the X-Forwarded-For header (on Cloudflare, CF-Connecting-IP). A private address such as 10.x or 192.168.x is a proxy, not a visitor: it's let through with a warning and never counted, so a setup mistake can't lock everyone out.
email
Optional, on signups: to catch throwaway addresses. It isn't kept.
failed
true after a wrong password or a declined payment, so it counts.
The answer
action is allow, challenge or block, with the reasons in plain words. It may also carry a warning (something to fix in how you call it) or a note (such as checkout protection being in the store plans). A wrong key or event gets an error and HTTP 4xx; a plan that ended answers allow, so your site keeps working.
No answer
If Ropeline doesn't answer within a second or two, let the visitor through: Ropeline should never be why a real customer can't log in or pay. The code on this page does that for you.
No code · Cloudflare

No code?
No problem.

Site behind Cloudflare? Put ropeline-worker.js in front of it. It asks Ropeline about each POST to the paths you list, answers the ones to block with "429 Too many tries", passes the rest on with an X-Ropeline-Action header, and tells Ropeline about the logins and payments your site turned down (from the status your site answers with).

  1. 1

    Create it

    In Cloudflare, go to Workers & Pages, Create, Start with Hello World, and paste the file in place of the example.

  2. 2

    Set it up

    Under Settings, Variables and Secrets, add ROPELINE_KEY (as a secret), ROPELINE_SITE (example.com) and ROPELINE_PATHS, such as {"/login": "login", "/signup": "signup", "/checkout": "checkout", "/contact": "form"}.

  3. 3

    Route it

    Under Settings, Domains and Routes, add a route: example.com/*.

Every platform

Which Ropeline is
right for you?

WordPress and WooCommerce

A plugin. No code.

  • Install it from WordPress.org
  • Free login protection, no time limit
  • Pro adds signups, checkout, forms and AI shoppers
Try Pro free How to install

Any website

One API call. Any language.

  • Node, PHP, Python, Ruby, Go
  • Allow, challenge or block, with reasons
  • Lets visitors through if Ropeline can't answer
Try it free for a week See the code

Behind Cloudflare

A Worker. No code changes.

  • Paste one file, add three settings
  • Bots get "429 Too many tries"
  • For any site that runs through Cloudflare
Try it free for a week Set it up
Pricing

One key.
The same plans.

Any Ropeline key works, on WordPress or anywhere else. Try any plan free for a week with just your email address: no card.

Site

$5 a month

One website

  • Logins, signups and forms
  • The Ropeline Network's shared list
  • Throwaway-email checks on signups
Try it free for a weekOr 14 days free with a card
For online stores

Store

From $15 a month

One online store, sized by paid orders a month

  • Everything in Site
  • Checkout: declined payments counted, card testers stopped
Try it free for a weekOr 14 days free with a card

Agency

$79 a month

Up to 50 websites

  • Everything in Store, on every site
  • One key for all your clients
Try it free for a weekOr 14 days free with a card

The Site plan covers logins, signups and forms on one website; the Store plans and Agency add checkout (card testing). Store plans are sized by orders a month, and Agency covers up to 50 websites, and Agency 10 up to 10 for $29 a month. See every plan.

What Ropeline keeps

Built to forget.

2 hours

The tries are counted under a scrambled copy of each visitor's address (mixed with your key and website), kept for 2 hours.

7 days

An address is only kept as it is when it reaches a block line, as your site's report to the shared list, for 7 days.

None

Email addresses are checked against the list of throwaway services and never kept. Ropeline never asks for passwords or card numbers.

More in the privacy policy.

FAQ

Questions.

Do I need WordPress?

No. Anything that can send one HTTPS request from your server works: Node, Deno, Bun, PHP, Python, Ruby, Go, Java or .NET. Or put a Cloudflare Worker in front of your site and change no code at all. On WordPress or WooCommerce, the plugin does all of this and more, with no code.

What if Ropeline is slow, or down?

Your visitors get through. The code on this page and the Worker wait at most 1.5 seconds, then let the visitor in. Ropeline should never be why a real customer can't log in or pay.

What do I do with a challenge?

Ask the visitor to prove they're human before you carry on: a CAPTCHA such as Cloudflare Turnstile, or a code by email. Real people pass and carry on.

Do you use it yourselves?

Yes. getropeline.com isn't WordPress, so it uses Ropeline for any website, with the same code as this page: our checkout, our free-week form and our dashboard sign-in ask Ropeline first. A blocked connection gets no card form. A challenged one is sent to the free week instead, where the link we email is the challenge.

Can I call it from the browser?

No, from your server only. The key is a password, and a bot controls everything that runs in its own browser.

Will testing use up my plan's website?

Not if you test with "site": "localhost": test copies of a site (localhost, and addresses ending in .local, .test or .localhost) never take one of your plan's places. Each real website does.

Does it work on Wix, Squarespace or Shopify?

Not yet. Those builders run their own logins and checkouts, with no place for your code to ask Ropeline first.

Bots wait outside.
On every website.

On WordPress or WooCommerce? Use the plugin instead: it does all this and more without any code. Questions: hello@getropeline.com.