Two Vercel features get called "the Vercel captcha", and only one of them is something a cookie can clear. By the end of this post you will be able to tell the Security Checkpoint from BotID, know why a plain HTTP script gets blocked on a Vercel-hosted site, and send a VercelToken task and replay its _vcrcs cookie.
Two protections, one platform
| Security Checkpoint | BotID | |
|---|---|---|
| What the visitor sees | An interstitial page that must be completed before the site loads | Nothing; it is invisible |
| How it works | A security challenge served in front of the site by Vercel Firewall / Attack Mode | A client-side machine-learning challenge, powered by Kasada according to Vercel's documentation |
| Where the decision is made | In front of the site, by Vercel Firewall | On the site's own server, which calls checkBotId() for the routes it protects |
| Who switches it on | The site owner, with Attack Mode or firewall custom rules | The site owner, typically for high-value routes |
Addressed by _vcrcs |
Yes | No |
Vercel documents BotID at a Basic level and a Deep Analysis level. Neither shows a challenge to the visitor, so there is nothing to see and nothing to click.
What scrapers actually hit
With Attack Mode on, Vercel challenges all traffic initiated by web browsers, API traffic included. Known bots such as search engines and webhooks pass through. Vercel also states that standalone APIs and other non-browser clients may not be able to pass the challenge and could be blocked.
That last sentence describes a plain requests or curl script exactly: a client that never runs the challenge is the kind of client Vercel warns about.
Site owners can also challenge arbitrary traffic with firewall custom rules, so a checkpoint can guard one route and not the next, and a URL that is challenged today may not be tomorrow. Always test the exact URL you plan to send.
Tell them apart
The sources behind this post do not document any reliable marker for the checkpoint page: no page title, HTML fragment or response header is published for it. The dependable test is the visible one.
- Open the exact URL in a normal browser through the proxy you plan to use.
- If an interstitial security page appears before the site content, you are facing the Security Checkpoint.
- If the page loads normally but your client is rejected on certain routes, BotID may be guarding those routes. What a rejected BotID request looks like on the wire is not documented in the sources, so confirm it in the Network panel rather than assuming.
The _vcrcs cookie
Passing the checkpoint leaves a cookie named _vcrcs on the protected origin. Its value is JWT-shaped, a long string that begins eyJ. It is replayed as a cookie, not typed into a form.
What nobody documents: how long it lives, whether it covers one route or the whole origin, and whether it is bound to the IP or user agent that earned it. Do not plan around a fixed lifetime.
Which result key holds _vcrcs
A SurSolver VercelToken result has exactly three keys: token, cookies and userAgent. Neither the catalog nor the research behind this post says which of them carries the _vcrcs value. It is not guessed here.
The helper below looks for _vcrcs inside cookies, accepting either an object keyed by cookie name or a list of {name, value} entries. If it is not there, it stops and tells you the shape of every key, so you can see where the value actually is after your first solve and then hardcode that one location.
Create the task and replay the cookie
websiteURL (the exact page that shows the checkpoint) and proxy are required; userAgent is an optional parameter, and the docs do not say whether the solve uses it, so do not count on it shaping the result. A four-part proxy string is read as user:password:host:port; see Proxies for all accepted formats.
Poll getTaskResult every 3 seconds. API errors come back as HTTP 200 with errorId: 1, and an unexpected fault is an HTTP 500 without errorId. The docs set no polling ceiling, so the script picks its own.
import os
import time
from urllib.parse import urlparse
import requests
API = 'https://api.sursolver.com'
API_KEY = os.environ['SURSOLVER_API_KEY']
POLL_SECONDS = 3
DEADLINE_SECONDS = 180
REQUEST_TIMEOUT = 30
COOKIE_NAME = '_vcrcs'
PREVIEW_CHARS = 12
TARGET_URL = 'https://www.example.com/'
USER_AGENT = (
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36'
)
PROXY = 'http://user:password@203.0.113.10:8000'
def call(path, body):
response = requests.post(f'{API}{path}', json=body, timeout=REQUEST_TIMEOUT)
response.raise_for_status() # HTTP 500 carries no errorId
data = response.json()
if data.get('errorId'):
raise RuntimeError(f"{data['errorCode']}: {data.get('errorDescription')}")
return data
def solve_vercel():
created = call('/createTask', {
'clientKey': API_KEY,
'task': {
'type': 'VercelToken',
'websiteURL': TARGET_URL,
'userAgent': USER_AGENT,
'proxy': PROXY,
},
})
task_id = created['taskId']
deadline = time.monotonic() + DEADLINE_SECONDS
while time.monotonic() < deadline:
time.sleep(POLL_SECONDS)
result = call('/getTaskResult', {'clientKey': API_KEY, 'taskId': task_id})
if result['status'] == 'ready':
return result['solution']
raise TimeoutError(f'task {task_id} still processing after {DEADLINE_SECONDS}s')
def find_vcrcs(solution):
cookies = solution.get('cookies')
if isinstance(cookies, dict) and cookies.get(COOKIE_NAME):
return cookies[COOKIE_NAME]
if isinstance(cookies, list):
for item in cookies:
if isinstance(item, dict) and item.get('name') == COOKIE_NAME:
return item['value']
shapes = {
key: f'{type(value).__name__}: {str(value)[:PREVIEW_CHARS]}'
for key, value in solution.items()
}
raise LookupError(f'{COOKIE_NAME} not found under cookies; solution is {shapes}')
solution = solve_vercel()
vcrcs = find_vcrcs(solution)
session = requests.Session()
session.proxies = {'http': PROXY, 'https': PROXY}
session.headers['User-Agent'] = solution.get('userAgent', USER_AGENT)
session.cookies.set(COOKIE_NAME, vcrcs, domain=urlparse(TARGET_URL).hostname)
response = session.get(TARGET_URL, timeout=REQUEST_TIMEOUT)
print(response.status_code)
userAgent in the result is conditional. When it is present, replay with it; when it is absent, the result does not say which browser the solve presented, so the script keeps your own client's user agent and you should confirm the first replay works before relying on it. Replaying through the same proxy is the cautious choice: the binding is undocumented, so keep it identical until you have measured otherwise.
If your own deadline expires, the task is still charged and still readable for the retention window (30 minutes from creation by default); fetch it later with the same taskId rather than creating a new one.
What the sources do not cover
Little about this protection is documented from the solving side, so several practical answers are simply unknown. Plan for them instead of filling the gaps with folklore:
- the lifetime of
_vcrcs, and whether it covers one route or the whole origin; - whether the cookie is bound to the IP or user agent that earned it;
- which of
token,cookiesanduserAgentcarries the value in a SurSolver result; - what a request rejected by BotID looks like, and whether clearing the checkpoint is enough on a route that BotID also guards;
- how the API reports a task for a URL that shows no checkpoint at all, since no error code specific to this type is documented.
The last point is why the browser check comes first: a task is charged when it is accepted, so measure on a handful of real URLs before you scale up.
Common failures
- No checkpoint at that URL. The task is charged when it is accepted and a task that finishes as failed automatically returns its original charge once, so confirm the interstitial in a browser first.
- No proxy. This type has no proxyless form; every task needs proxy details.
- Treating BotID like the checkpoint. A
_vcrcscookie addresses only the visible interstitial. If a route is also gated by BotID, clearing the checkpoint may not be enough, and the sources do not say whether it ever is. - Replaying from somewhere else. A different network or user agent than the solve used is the first thing to rule out when a fresh solve is refused.
- Reusing the cookie after it stops working. With the lifetime undocumented, when the checkpoint reappears, run a new solve rather than retrying the old value.
- Expecting a bare script to pass. Vercel says non-browser clients may be blocked under Attack Mode. Solving the challenge and replaying
_vcrcsis the documented route; the sources do not promise that a bare script then passes every request.
Next steps
Confirm the checkpoint in a browser, then send a VercelToken task with websiteURL, proxy and optionally userAgent, and look at which result key holds _vcrcs on your first solve. The Vercel guide covers recognition and parameters, and the public catalog (GET /v1/task-types) lists the accepted names and result keys. For the proxy and header side of replaying cookies, read proxies for captcha solving and browser headers and user agents.