Cloudflare Workers

Cloudflare Edge Workers

Install

npm / Wrangler

Install the package:

1
npm i @botbye/cloudflare-worker

Requires @cloudflare/workers-types >= 4 as a peer dependency.

Dashboard

Paste the pre-built bundle directly into your worker's index.js. No build step required — the SDK is available as BotBye.init, BotBye.evaluate, etc.:

1
var BotBye=(()=>{var Jt=Object.create;var Q=Object.defineProperty;var Yt=Object.getOwnPropertyDescriptor;var Xt=Object.getOwnPropertyNames;var Zt=Object.getPrototypeOf,er=Object.prototype.hasOwnProperty;var u=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),tr=(e,t)=>{for(var i in t)Q(e,i,{get:t[i],enumerable:!0})},je=(e,t,i,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Xt(t))!er.call(e,n)&&n!==i&&Q(e,n,{get:()=>t[n],enumerable:!(r=Yt(t,n))||r.enumerable});return e};var z=(e,t,i)=>(i=e!=null?Jt(Zt(e)):{},je(t||!e||!e.__esModule?Q(i,"default",{value:e,enumerable:!0}):i,e)),rr=e=>je(Q({},"__esModule",{value:!0}),e);var l=u(c=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});c.getModuleHeaders=c.once=c.getIpFromHeaders=c.extractMessageFromError=c.isObject=c.isNumber=c.withTimeout=c.isKeyExist=c.toString=c.isNotNil=c.isPlainObject=c.isString=void 0;var De=(e,t)=>typeof e===t,ir=e=>e==null,nr=e=>!ir(e);c.isNotNil=nr;var Te=e=>De(e,"string");c.isString=Te;var sr=e=>De(e,"number")&&!Number.isNaN(e);c.isNumber=sr;var pe=e=>!(typeof e!="object"||e===null);c.isObject=pe;var or=e=>{if(typeof e!="object"||e===null)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null};c.isPlainObject=or;var ur=e=>Te(e)?e:String(e);c.toString=ur;var ar=(e,t)=>pe(e)&&t in e;c.isKeyExist=ar;var cr=(e,t,i)=>{let r=new Promise((n,s)=>setTimeout(()=>{s(i)},t));return Promise.race([e,r])};c.withTimeout=cr;var lr=e=>{let t="Unknown error";return e instanceof Error&&(t=e.message),t};c.extractMessageFromError=lr;var we=e=>{var t;let i=Array.isArray(e)?e[0]:e;return Te(i)&&((t=i.split(",")[0])===null||t===void 0?void 0:t.trim())||null},dr=e=>{var t;return pe(e)?(t=we(e["x-forwarded-for"]))!==null&&t!==void 0?t:we(e["x-real-ip"]):null};c.getIpFromHeaders=dr;var Er=e=>{let t=!1;return((...i)=>{t||(t=!0,e(...i))})};c.once=Er;var _r=e=>({"Module-Name":e.module.name,"Module-Version":e.module.version});c.getModuleHeaders=_r});var He=u($=>{"use strict";Object.defineProperty($,"__esModule",{value:!0});$.LOGGER_LEVELS=void 0;var fr=["error","warn","info","debug","log"];$.LOGGER_LEVELS=fr});var Ne=u(v=>{"use strict";Object.defineProperty(v,"__esModule",{value:!0});v.DEFAULT_LOG_LEVEL=v.isLoggerLevelValid=v.redeclareLogger=v.decorateLogger=void 0;var ye=l(),L=He(),Ke=()=>{},Ie=(e,t)=>L.LOGGER_LEVELS.reduce((i,r)=>{var n;let s=((n=t[r])!==null&&n!==void 0?n:Ke).bind(t);return i[r]=e(r,s),i},t),hr=e=>L.LOGGER_LEVELS.reduce((t,i)=>{let r=(0,ye.isObject)(e)?e[i]:void 0;return t[i]=(r??Ke).bind(e),t},{}),Or=e=>Ie((i,r)=>(...n)=>{r("[BotBye!]",...n)},e),Rr=e=>{let t=(n,s)=>L.LOGGER_LEVELS.indexOf(s)<=L.LOGGER_LEVELS.indexOf(n),r=Ie((n,s)=>function(...o){t(this.level,n)&&s(...o)},e);return r.setLevel=function(n){this.level=n},r},vr=e=>Ie((i,r)=>(...n)=>{try{r(...n)}catch{}},e),gr="info";v.DEFAULT_LOG_LEVEL=gr;var ke=e=>vr(Rr(Or(hr(e))));v.decorateLogger=ke;var Tr=e=>(0,ye.isPlainObject)(e)?L.LOGGER_LEVELS.every(t=>typeof e[t]=="function"):!1,pr=(e,t)=>Tr(e)?ke(e):t;v.redeclareLogger=pr;var yr=e=>(0,ye.isString)(e)?L.LOGGER_LEVELS.includes(e):!1;v.isLoggerLevelValid=yr});var Le=u(S=>{"use strict";Object.defineProperty(S,"__esModule",{value:!0});S.MODULE_VERSION=S.MODULE_NAME=void 0;var Ir="NODE_CORE";S.MODULE_NAME=Ir;var Nr="2.2.1";S.MODULE_VERSION=Nr});var We=u(J=>{"use strict";Object.defineProperty(J,"__esModule",{value:!0});J.sendInitCall=void 0;var xe=l(),Ge="Invalid JSON in the server response",Lr=({global:e})=>{let{result:t,abort:i}=e.httpClient.call(e.url+"/init-request/v1",{method:"POST",headers:(0,xe.getModuleHeaders)(e),body:{serverKey:e.serverKey}});return{result:t.then(n=>{let s={error:"Unknown error"};try{s=JSON.parse(n)}catch{throw new Error(Ge)}if(!(0,xe.isPlainObject)(s))throw new Error(Ge);if("status"in s&&s.status==="ok")return"";if("error"in s)throw new Error(s.error);return""}),abort:i}};J.sendInitCall=Lr});var C=u(m=>{"use strict";Object.defineProperty(m,"__esModule",{value:!0});m.REQUEST_TIMEOUT_ERROR_CODE=m.REQUEST_ERROR_CODE=void 0;var Sr="REQUEST_ERROR";m.REQUEST_ERROR_CODE=Sr;var mr="REQUEST_TIMEOUT";m.REQUEST_TIMEOUT_ERROR_CODE=mr});var g=u(f=>{"use strict";Object.defineProperty(f,"__esModule",{value:!0});f.classifyRequestError=f.SDK_ERROR=f.JSON_ERROR=f.CONNECTION_ERROR=f.TIMEOUT_ERROR=f.UNKNOWN_ERROR=void 0;var Cr=l(),Be=C(),Qe="unknown error";f.UNKNOWN_ERROR=Qe;var ze="timeout";f.TIMEOUT_ERROR=ze;var $e="connection error";f.CONNECTION_ERROR=$e;var qr="invalid json response";f.JSON_ERROR=qr;var Ur="SDK error";f.SDK_ERROR=Ur;var Ar=e=>(0,Cr.isString)(e)?e:e instanceof Error?e.message:"UNKNOWN",Fr=e=>{let t=Ar(e);return t.includes(Be.REQUEST_TIMEOUT_ERROR_CODE)?ze:t.includes(Be.REQUEST_ERROR_CODE)?$e:Qe};f.classifyRequestError=Fr});var Se=u(Y=>{"use strict";Object.defineProperty(Y,"__esModule",{value:!0});Y.BASE_REQUEST_TIMEOUT=void 0;var br=1500;Y.BASE_REQUEST_TIMEOUT=br});var Je=u(X=>{"use strict";Object.defineProperty(X,"__esModule",{value:!0});X.sendEvaluateCall=void 0;var Pr=l(),Mr="/api/v1/protect/evaluate",Vr=({global:e,event:t})=>{var i;let r=`${e.url}${Mr}`;try{r=new URL(`${r}?${(i=t.request.token)!==null&&i!==void 0?i:""}`).href}catch{}return e.httpClient.call(r,{method:"POST",headers:(0,Pr.getModuleHeaders)(e),body:Object.assign(Object.assign({},t),{server_key:e.serverKey})})};X.sendEvaluateCall=Vr});var me=u(q=>{"use strict";Object.defineProperty(q,"__esModule",{value:!0});q.EVENT_INFO_EVENT_STATUSES=q.EVENT_VALIDATION_TYPE=void 0;var jr=["SUCCESSFUL","FAILED","ATTEMPTED","UNKNOWN"];q.EVENT_INFO_EVENT_STATUSES=jr;var wr=["validate","risk","full"];q.EVENT_VALIDATION_TYPE=wr});var D=u(h=>{"use strict";Object.defineProperty(h,"__esModule",{value:!0});h.fieldRequiredLog=h.sanitizeIfPlainObject=h.sanitizeIfTruthy=h.sanitizeIfNotNil=h.sanitize=void 0;var Ce=l(),Ye=(e,t,i,r,n,s,o)=>{if((0,Ce.isKeyExist)(e,i)){t[r]=n(e[i],s);return}o&&(t[r]=o)};h.sanitize=Ye;var Dr=(e,t,i,r,n,s,o)=>{if((0,Ce.isNotNil)(e[i])){Ye(e,t,i,r,n,s,o);return}o&&(t[r]=o)};h.sanitizeIfNotNil=Dr;var Hr=(e,t,i,r,n,s,o)=>{if(e[i]){t[r]=n(e[i],s);return}o&&(t[r]=o)};h.sanitizeIfTruthy=Hr;var Kr=(e,t,i,r,n,s,o)=>{if((0,Ce.isPlainObject)(e[i])){t[r]=n(e[i],s);return}o&&(t[r]=o)};h.sanitizeIfPlainObject=Kr;var kr=(e,t,i,r)=>{e[i]||t.warn(`Field ${i} is required in ${r}`)};h.fieldRequiredLog=kr});var tt=u(T=>{"use strict";Object.defineProperty(T,"__esModule",{value:!0});T.adaptRequestInfoWithOptionalHeaders=T.adaptRequestInfoWithRequiredHeaders=T.FALLBACK_EVENT_REQUEST_INFO=void 0;var N=l(),I=D(),Xe="0.0.0.0",Ze={},xr={ip:Xe,headers:Ze};T.FALLBACK_EVENT_REQUEST_INFO=xr;var Gr=e=>{if(!(0,N.isPlainObject)(e))return Ze;let t={};for(let i of Object.keys(e))t[i]=(0,N.toString)(e[i]);return t},et=e=>(t,i)=>{let r=(0,N.isPlainObject)(t)?t:{},n={};return(0,I.fieldRequiredLog)(r,i.logger,"ip","evaluateEvent.request"),e&&(0,I.fieldRequiredLog)(r,i.logger,"headers","evaluateEvent.request"),(0,I.sanitizeIfTruthy)(r,n,"ip","ip",N.toString,i,Xe),(0,I.sanitizeIfNotNil)(r,n,"token","token",N.toString,i),(0,I.sanitizeIfPlainObject)(r,n,"headers","headers",Gr,i,{}),(0,I.sanitizeIfNotNil)(r,n,"requestMethod","request_method",N.toString,i),(0,I.sanitizeIfNotNil)(r,n,"requestUri","request_uri",N.toString,i),n},Wr=et(!0);T.adaptRequestInfoWithRequiredHeaders=Wr;var Br=et(!1);T.adaptRequestInfoWithOptionalHeaders=Br});var nt=u(U=>{"use strict";Object.defineProperty(U,"__esModule",{value:!0});U.adaptEventInfo=U.FALLBACK_EVENT_EVENT_INFO=void 0;var qe=l(),rt=me(),Z=D(),Ue="UNKNOWN",it="UNKNOWN",Qr={type:it,status:Ue};U.FALLBACK_EVENT_EVENT_INFO=Qr;var zr=(e,t)=>{let i=(0,qe.toString)(e).trim().toUpperCase();return rt.EVENT_INFO_EVENT_STATUSES.includes(i)?i:(t.logger.warn(`Event status should be one of: ${rt.EVENT_INFO_EVENT_STATUSES.join(" || ")}`),Ue)},$r=(e,t)=>{let i=(0,qe.isPlainObject)(e)?e:{},r={};return(0,Z.fieldRequiredLog)(i,t.logger,"type","evaluateEvent.event"),(0,Z.fieldRequiredLog)(i,t.logger,"status","evaluateEvent.event"),(0,Z.sanitize)(i,r,"type","type",qe.toString,t,it),(0,Z.sanitizeIfTruthy)(i,r,"status","status",zr,t,Ue),r};U.adaptEventInfo=$r});var ot=u(A=>{"use strict";Object.defineProperty(A,"__esModule",{value:!0});A.adaptUserInfo=A.FALLBACK_EVENT_USER_INFO=void 0;var H=l(),K=D(),st="00000000-0000-0000-0000-000000000000",Jr={account_id:st};A.FALLBACK_EVENT_USER_INFO=Jr;var Yr=(e,t)=>{let i=(0,H.isPlainObject)(e)?e:{},r={};return(0,K.fieldRequiredLog)(i,t.logger,"accountId","evaluateEvent.user"),(0,K.sanitizeIfTruthy)(i,r,"accountId","account_id",H.toString,t,st),(0,K.sanitizeIfNotNil)(i,r,"username","username",H.toString,t),(0,K.sanitizeIfNotNil)(i,r,"email","email",H.toString,t),(0,K.sanitizeIfNotNil)(i,r,"phone","phone",H.toString,t),r};A.adaptUserInfo=Yr});var at=u(ee=>{"use strict";Object.defineProperty(ee,"__esModule",{value:!0});ee.adaptCustomFields=void 0;var ut=l(),Xr={},Zr=(e,t)=>{if(!(0,ut.isPlainObject)(e))return Xr;let i={};for(let r of Object.keys(e))i[r]=(0,ut.toString)(e[r]);return i};ee.adaptCustomFields=Zr});var ft=u(te=>{"use strict";Object.defineProperty(te,"__esModule",{value:!0});te.adaptEvent=void 0;var ct=l(),lt=me(),R=D(),k=tt(),dt=nt(),Et=ot(),_t=at(),ei=(e,t)=>{let i=(0,ct.isPlainObject)(e)?e:{},r={},n=String(i.type).trim().toLowerCase();if(lt.EVENT_VALIDATION_TYPE.includes(n)||(t.logger.warn(`Event type should be one of: ${lt.EVENT_VALIDATION_TYPE.join(" || ")}`),n="validate"),r.type=n,n==="validate"&&((0,R.fieldRequiredLog)(i,t.logger,"request","validate event"),(0,R.sanitize)(i,r,"request","request",k.adaptRequestInfoWithRequiredHeaders,t,k.FALLBACK_EVENT_REQUEST_INFO),(0,R.sanitizeIfNotNil)(i,r,"customFields","custom_fields",_t.adaptCustomFields,t)),n==="full"||n==="risk"){(0,R.fieldRequiredLog)(i,t.logger,"request",`${n} event`),(0,R.fieldRequiredLog)(i,t.logger,"event",`${n} event`),(0,R.fieldRequiredLog)(i,t.logger,"user",`${n} event`);let s=n==="risk"?k.adaptRequestInfoWithOptionalHeaders:k.adaptRequestInfoWithRequiredHeaders;(0,R.sanitize)(i,r,"request","request",s,t,k.FALLBACK_EVENT_REQUEST_INFO),(0,R.sanitize)(i,r,"event","event",dt.adaptEventInfo,t,dt.FALLBACK_EVENT_EVENT_INFO),(0,R.sanitize)(i,r,"user","user",Et.adaptUserInfo,t,Et.FALLBACK_EVENT_USER_INFO),(0,R.sanitizeIfNotNil)(i,r,"customFields","custom_fields",_t.adaptCustomFields,t)}return n==="risk"&&(0,R.sanitizeIfNotNil)(i,r,"botbyeResult","botbye_result",ct.toString,t),r};te.adaptEvent=ei});var Ot=u(re=>{"use strict";Object.defineProperty(re,"__esModule",{value:!0});re.httpClientCallWithTimeout=void 0;var ti=l(),ht=C(),ri=Se(),ii=(e,t,i=ri.BASE_REQUEST_TIMEOUT,r)=>({result:(0,ti.withTimeout)(e.result,i,ht.REQUEST_TIMEOUT_ERROR_CODE).catch(s=>{var o;throw s===ht.REQUEST_TIMEOUT_ERROR_CODE&&(t.logger.debug(`The request ${(o=r?.name)!==null&&o!==void 0?o:""} was interrupted by a timeout`),e.abort()),s}),abort:e.abort});re.httpClientCallWithTimeout=ii});var Rt=u(ie=>{"use strict";Object.defineProperty(ie,"__esModule",{value:!0});ie.EVALUATE_DECISIONS=void 0;var ni=["ALLOW","BLOCK","CHALLENGE"];ie.EVALUATE_DECISIONS=ni});var x=u(ne=>{"use strict";Object.defineProperty(ne,"__esModule",{value:!0});ne.createFallbackEvaluationResult=void 0;var si=e=>({decision:"ALLOW",error:{message:e}});ne.createFallbackEvaluationResult=si});var vt=u(ue=>{"use strict";Object.defineProperty(ue,"__esModule",{value:!0});ue.withApiErrorHandle=void 0;var p=l(),se=g(),oi=Rt(),oe=x(),ui=e=>!(!(0,p.isPlainObject)(e)||!("request_id"in e)||!(0,p.isString)(e.request_id)||!("decision"in e)||!(0,p.isString)(e.decision)||!oi.EVALUATE_DECISIONS.includes(e.decision)||!("risk_score"in e)||!(0,p.isNumber)(e.risk_score)||!("signals"in e)||!Array.isArray(e.signals)||!("scores"in e)||!(0,p.isPlainObject)(e.scores)||"botbye_result"in e&&!(0,p.isString)(e.botbye_result)||"challenge"in e&&(!(0,p.isPlainObject)(e.challenge)||!(0,p.isString)(e.challenge.type))),ai=(e,t)=>new Promise(i=>{try{e().result.then(r=>{let n=null;try{n=JSON.parse(r)}catch{i((0,oe.createFallbackEvaluationResult)(se.JSON_ERROR));return}if(!ui(n)){i((0,oe.createFallbackEvaluationResult)(se.JSON_ERROR));return}i(n)}).catch(r=>{i((0,oe.createFallbackEvaluationResult)((0,se.classifyRequestError)(r)))})}catch{i((0,oe.createFallbackEvaluationResult)(se.SDK_ERROR))}});ue.withApiErrorHandle=ai});var gt=u(ae=>{"use strict";Object.defineProperty(ae,"__esModule",{value:!0});ae.evaluateFactory=void 0;var ci=l(),li=g(),di=Se(),Ei=Je(),_i=ft(),fi=Ot(),hi=vt(),Oi=x(),Ri=e=>t=>e.serverKey?(0,hi.withApiErrorHandle)(()=>{var i;return(0,fi.httpClientCallWithTimeout)((0,Ei.sendEvaluateCall)({global:e,event:(0,_i.adaptEvent)(t,e)}),e,(0,ci.isNumber)((i=e.timeouts)===null||i===void 0?void 0:i.evaluate)?e.timeouts.evaluate:di.BASE_REQUEST_TIMEOUT,{name:"EVALUATE"})},e):(e.logger.error("serverKey is not set. Check the init function call."),Promise.resolve((0,Oi.createFallbackEvaluationResult)(li.SDK_ERROR)));ae.evaluateFactory=Ri});var Tt=u(ce=>{"use strict";Object.defineProperty(ce,"__esModule",{value:!0});ce.evaluateWithExtractorFactory=void 0;var vi=l(),gi=e=>(0,vi.isKeyExist)(e,"request"),Ti=(e,t,i)=>r=>{var n;if(gi(r.request)){let s=e(r.request.request,i);r.request=Object.assign(Object.assign({},s),{token:(n=r.request.token)!==null&&n!==void 0?n:s.token})}return t(r)};ce.evaluateWithExtractorFactory=Ti});var pt=u(le=>{"use strict";Object.defineProperty(le,"__esModule",{value:!0});le.withAnyErrorHandle=void 0;var pi=g(),yi=x(),Ii=e=>t=>new Promise(i=>{try{i(e(t))}catch{i((0,yi.createFallbackEvaluationResult)(pi.SDK_ERROR))}});le.withAnyErrorHandle=Ii});var yt=u(F=>{"use strict";Object.defineProperty(F,"__esModule",{value:!0});F.isTimeoutsValid=F.assertInitOptions=void 0;var G=l(),Ni=e=>!(!(0,G.isPlainObject)(e)||"evaluate"in e&&!(0,G.isNumber)(e.evaluate));F.isTimeoutsValid=Ni;var Li=e=>{if(!(0,G.isPlainObject)(e))throw new Error("initOptions must be an object");if(!(0,G.isString)(e.serverKey)||e.serverKey.length===0)throw new Error("initOptions.serverKey must be a non-empty string");if(e.url!=null&&(!(0,G.isString)(e.url)||e.url.length===0))throw new Error("initOptions.url must be a non-empty string")};F.assertInitOptions=Li});var Lt=u(de=>{"use strict";Object.defineProperty(de,"__esModule",{value:!0});de.moduleApiFactory=void 0;var b=Ne(),Ae=l(),It=Le(),Si=We(),mi=gt(),Ci=Tt(),qi=pt(),Nt=yt(),Ui=e=>{var t;let i=(0,b.decorateLogger)(console);i.setLevel(b.DEFAULT_LOG_LEVEL);let r={url:(t=e.url)!==null&&t!==void 0?t:"https://verify.botbye.com",serverKey:"",module:e.module||{name:It.MODULE_NAME,version:It.MODULE_VERSION},httpClient:e.httpClient,logger:i},n=(0,mi.evaluateFactory)(r);if(e.requestInfoExtractor){let d=e.requestInfoExtractor;n=(0,Ci.evaluateWithExtractorFactory)(d,n,r)}let s=(0,Ae.once)(()=>{(0,Si.sendInitCall)({global:r}).result.then(()=>{r.logger.info("Inited successfully")}).catch(d=>{r.logger.warn(`Can't send init call: ${(0,Ae.extractMessageFromError)(d)}`)})}),o=()=>{if(!r.serverKey){r.logger.warn("Init call skipped: serverKey is not set. Check the init function call.");return}s()};return{init:(0,Ae.once)(d=>{var a,E;(0,Nt.assertInitOptions)(d),r.url=d.url||r.url,r.serverKey=d.serverKey,i=(0,b.redeclareLogger)((a=d.logger)===null||a===void 0?void 0:a.logger,i),r.logger=i,i.setLevel((0,b.isLoggerLevelValid)((E=d.logger)===null||E===void 0?void 0:E.level)?d.logger.level:b.DEFAULT_LOG_LEVEL),r.timeouts=(0,Nt.isTimeoutsValid)(d.timeouts)?d.timeouts:void 0,e.skipInitCall||o()}),evaluate:(0,qi.withAnyErrorHandle)(n),dev:{setLoggerLevel:d=>{(0,b.isLoggerLevelValid)(d)&&i.setLevel(d)},sendInitCall:o,getLogger:()=>i}}};de.moduleApiFactory=Ui});var Fe=u(y=>{"use strict";Object.defineProperty(y,"__esModule",{value:!0});y.phishingErrorStatus=y.PHISHING_REQUEST_TIMEOUT=y.DEFAULT_PHISHING_ENDPOINT=void 0;var Ai=g(),Fi="https://verify.botbye.com";y.DEFAULT_PHISHING_ENDPOINT=Fi;var bi=5e3;y.PHISHING_REQUEST_TIMEOUT=bi;var Pi=504,Mi=502,Vi=e=>e===Ai.TIMEOUT_ERROR?Pi:Mi;y.phishingErrorStatus=Vi});var St=u(Ee=>{"use strict";Object.defineProperty(Ee,"__esModule",{value:!0});Ee.sendPhishingInitCall=void 0;var ji=g(),wi=l(),Di=({global:e})=>{let{result:t,abort:i}=e.httpClient.call(e.url+`/api/v1/phishing/init-request/v1/${encodeURIComponent(e.clientKey)}`,{method:"POST",headers:(0,wi.getModuleHeaders)(e)});return t.then(r=>{(r.status<200||r.status>=300)&&e.logger.warn(`phishing init-request returned HTTP ${r.status}`)}).catch(r=>{e.logger.warn(`phishing init-request failed: ${(0,ji.classifyRequestError)(r)}`)}),{result:t,abort:i}};Ee.sendPhishingInitCall=Di});var Ut=u(_e=>{"use strict";Object.defineProperty(_e,"__esModule",{value:!0});_e.sendPhishingCatcherCall=void 0;var be=l(),mt=C(),Hi=g(),Ct=Fe(),qt=(e,t)=>{var i;let r=(i=e[t])!==null&&i!==void 0?i:e[t.toLowerCase()];if(!(0,be.isString)(r))return null;let n=r.trim();return n===""||n.toLowerCase()==="null"?null:n},Ki=(e,t)=>{var i;let r=(0,be.getModuleHeaders)(e),n=(i=t.headers)!==null&&i!==void 0?i:{},s=qt(n,"Origin"),o=qt(n,"Referer");return s&&(r.Origin=s),o&&(r.Referer=o),r},ki=(e,t)=>{var i,r;let n=new URL(`${e.url}/api/v1/phishing/image/${encodeURIComponent(e.clientKey)}/server`);n.searchParams.set("format",t.format),t.format==="svg"&&(n.searchParams.set("image_id",t.innerPngUrl),t.skipExecution===!0&&n.searchParams.set("executable","false"));let{result:s,abort:o}=e.httpClient.call(n.toString(),{method:"GET",headers:Ki(e,t)});return(0,be.withTimeout)(s,(r=(i=e.timeouts)===null||i===void 0?void 0:i.fetchCatcher)!==null&&r!==void 0?r:Ct.PHISHING_REQUEST_TIMEOUT,mt.REQUEST_TIMEOUT_ERROR_CODE).catch(d=>{d===mt.REQUEST_TIMEOUT_ERROR_CODE&&o();let a=(0,Hi.classifyRequestError)(d);return e.logger.warn(`Phishing catcher fetch failed: ${a}`),{status:(0,Ct.phishingErrorStatus)(a),headers:{},body:new Uint8Array(0),error:{message:a}}})};_e.sendPhishingCatcherCall=ki});var At=u(P=>{"use strict";Object.defineProperty(P,"__esModule",{value:!0});P.assertInitOptions=P.isTimeoutsValid=void 0;var W=l(),xi=e=>!(!(0,W.isPlainObject)(e)||"fetchCatcher"in e&&!(0,W.isNumber)(e.fetchCatcher));P.isTimeoutsValid=xi;var Gi=e=>{if(!(0,W.isPlainObject)(e))throw new Error("initOptions must be an object");if(!(0,W.isString)(e.clientKey)||e.clientKey.length===0)throw new Error("initOptions.clientKey must be a non-empty string");if(e.url!=null&&(!(0,W.isString)(e.url)||e.url.length===0))throw new Error("initOptions.url must be a non-empty string")};P.assertInitOptions=Gi});var jt=u(V=>{"use strict";var Wi=V&&V.__rest||function(e,t){var i={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(i[r]=e[r]);if(e!=null&&typeof Object.getOwnPropertySymbols=="function")for(var n=0,r=Object.getOwnPropertySymbols(e);n<r.length;n++)t.indexOf(r[n])<0&&Object.prototype.propertyIsEnumerable.call(e,r[n])&&(i[r[n]]=e[r[n]]);return i};Object.defineProperty(V,"__esModule",{value:!0});V.phishingModuleApiFactory=void 0;var Ft=Le(),M=Ne(),B=l(),bt=g(),Vt=Fe(),Bi=St(),Qi=Ut(),Pt=At(),Mt={status:(0,Vt.phishingErrorStatus)(bt.SDK_ERROR),headers:{},body:new Uint8Array(0),error:{message:bt.SDK_ERROR}},zi=e=>{var t;let i=(0,M.decorateLogger)(console);i.setLevel(M.DEFAULT_LOG_LEVEL);let r={url:(t=e.url)!==null&&t!==void 0?t:Vt.DEFAULT_PHISHING_ENDPOINT,clientKey:"",module:e.module||{name:Ft.MODULE_NAME,version:Ft.MODULE_VERSION},httpClient:e.httpClient,logger:i},n=(0,B.once)(()=>{(0,Bi.sendPhishingInitCall)({global:r}).result.then(()=>{r.logger.info("Phishing inited successfully")}).catch(a=>{r.logger.warn(`Can't send phishing init call: ${(0,B.extractMessageFromError)(a)}`)})}),s=()=>{if(!r.clientKey){r.logger.warn("Phishing init call skipped: clientKey is not set. Check the init function call.");return}n()},o=(0,B.once)(a=>{var E,_;(0,Pt.assertInitOptions)(a),r.url=a.url||r.url,r.clientKey=a.clientKey,i=(0,M.redeclareLogger)((E=a.logger)===null||E===void 0?void 0:E.logger,i),r.logger=i,i.setLevel((0,M.isLoggerLevelValid)((_=a.logger)===null||_===void 0?void 0:_.level)?a.logger.level:M.DEFAULT_LOG_LEVEL),r.timeouts=(0,Pt.isTimeoutsValid)(a.timeouts)?a.timeouts:void 0,e.skipInitCall||s()});return{fetchCatcher:a=>{try{if(!r.clientKey)return r.logger.error("fetchCatcher called before init; call init({ clientKey }) first"),Promise.resolve(Mt);let E;if(e.catcherRequestInfoExtractor&&(0,B.isKeyExist)(a,"request")){let _=a,{request:w}=_,zt=Wi(_,["request"]),{headers:$t}=e.catcherRequestInfoExtractor(w,r);E=Object.assign(Object.assign({},zt),{headers:$t})}else E=a;return(0,Qi.sendPhishingCatcherCall)(r,E)}catch(E){return r.logger.error(`fetchCatcher failed unexpectedly: ${(0,B.extractMessageFromError)(E)}`),Promise.resolve(Mt)}},init:o,dev:{sendInitCall:s,getLogger:()=>i,setLoggerLevel:a=>{(0,M.isLoggerLevelValid)(a)&&i.setLevel(a)}}}};V.phishingModuleApiFactory=zi});var wt=u(fe=>{"use strict";Object.defineProperty(fe,"__esModule",{value:!0});fe.phishingModuleApiFactory=void 0;var $i=jt();Object.defineProperty(fe,"phishingModuleApiFactory",{enumerable:!0,get:function(){return $i.phishingModuleApiFactory}})});var Pe=u(O=>{"use strict";Object.defineProperty(O,"__esModule",{value:!0});O.phishingModuleApiFactory=O.SDK_ERROR=O.createFallbackEvaluationResult=O.getIpFromHeaders=O.moduleApiFactory=void 0;var Ji=Lt();Object.defineProperty(O,"moduleApiFactory",{enumerable:!0,get:function(){return Ji.moduleApiFactory}});var Yi=l();Object.defineProperty(O,"getIpFromHeaders",{enumerable:!0,get:function(){return Yi.getIpFromHeaders}});var Xi=x();Object.defineProperty(O,"createFallbackEvaluationResult",{enumerable:!0,get:function(){return Xi.createFallbackEvaluationResult}});var Zi=g();Object.defineProperty(O,"SDK_ERROR",{enumerable:!0,get:function(){return Zi.SDK_ERROR}});var en=wt();Object.defineProperty(O,"phishingModuleApiFactory",{enumerable:!0,get:function(){return en.phishingModuleApiFactory}})});var Me=u(he=>{"use strict";Object.defineProperty(he,"__esModule",{value:!0});he.bodyToJson=void 0;var tn=e=>{if(e!=null)return typeof e=="string"?e:JSON.stringify(e)};he.bodyToJson=tn});var Ht=u(Oe=>{"use strict";Object.defineProperty(Oe,"__esModule",{value:!0});Oe.fetchHttpClient=void 0;var rn=l(),nn=Me(),Dt=C(),sn={type:"fetchHttpClient",call(e,t){let i=new AbortController,r=()=>{i.abort()};return{result:fetch(e,{method:t.method,body:(0,nn.bodyToJson)(t.body),headers:t.headers,keepalive:t.keepalive,signal:i.signal}).then(s=>{var o;if(!s.ok)throw(o=s.body)===null||o===void 0||o.cancel(),new Error(Dt.REQUEST_ERROR_CODE);return s.text()}).catch(s=>{throw new Error(`${Dt.REQUEST_ERROR_CODE} ${(0,rn.extractMessageFromError)(s)}`)}),abort:r}}};Oe.fetchHttpClient=sn});var Kt=u(j=>{"use strict";var on=j&&j.__awaiter||function(e,t,i,r){function n(s){return s instanceof i?s:new i(function(o){o(s)})}return new(i||(i=Promise))(function(s,o){function d(_){try{E(r.next(_))}catch(w){o(w)}}function a(_){try{E(r.throw(_))}catch(w){o(w)}}function E(_){_.done?s(_.value):n(_.value).then(d,a)}E((r=r.apply(e,t||[])).next())})};Object.defineProperty(j,"__esModule",{value:!0});j.fetchPhishingHttpClient=void 0;var un=l(),an=C(),cn=Me(),ln={type:"fetchPhishingHttpClient",call(e,t){let i=new AbortController,r=()=>{i.abort()};return{result:fetch(e,{method:t.method,headers:t.headers,body:(0,cn.bodyToJson)(t.body),signal:i.signal}).then(s=>on(this,void 0,void 0,function*(){let o={};s.headers.forEach((a,E)=>{o[E]=a});let d=yield s.arrayBuffer();return{status:s.status,headers:o,body:new Uint8Array(d)}})).catch(s=>{throw new Error(`${an.REQUEST_ERROR_CODE} ${(0,un.extractMessageFromError)(s)}`)}),abort:r}}};j.fetchPhishingHttpClient=ln});var fn={};tr(fn,{dev:()=>_n,evaluate:()=>En,factory:()=>Qt,init:()=>dn,phishing:()=>Gt,phishingFactory:()=>Ve});var Wt=z(Pe()),Bt=z(Ht());var Re="CF_WORKER",ve="2.2.0";var ge=(e,t)=>{try{let i={};return e.headers.forEach((r,n)=>{i[n]=r}),{ip:e.headers.get("CF-Connecting-IP")??"0.0.0.0",requestUri:new URL(e.url).pathname,requestMethod:e.method,headers:i}}catch{return t.logger.warn("Not valid type of request passed. event.request.request should be an instance of CF Workers Request. Fallback value will be used"),{ip:"0.0.0.0",headers:{}}}};var kt=z(Pe()),xt=z(Kt());var Ve=()=>{let e=(0,kt.phishingModuleApiFactory)({httpClient:xt.fetchPhishingHttpClient,module:{name:Re,version:ve},skipInitCall:!0,catcherRequestInfoExtractor:ge});return{...e,fetchCatcher:(...t)=>(e.dev.sendInitCall(),e.fetchCatcher(...t))}},Gt=Ve();var Qt=()=>{let e=(0,Wt.moduleApiFactory)({httpClient:Bt.fetchHttpClient,module:{name:Re,version:ve},skipInitCall:!0,requestInfoExtractor:ge});return{...e,evaluate:(...t)=>(e.dev.sendInitCall(),e.evaluate(...t))}},{init:dn,evaluate:En,dev:_n}=Qt();return rr(fn);})();

Configuration

Call init at module load time, before the fetch handler runs:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { init } from "@botbye/cloudflare-worker";
// Or if integrating via the Dashboard:
// const { init } = BotBye;

init({
  serverKey: "00000000-0000-0000-0000-000000000000", // Use your project server-key
});

export default {
  async fetch(request, env, ctx) {
    // ...
  },
};

init() itself makes no network calls — it only stores configuration. The connection handshake with BotBye is deferred to the first evaluate() call.

init options

Option Type Required Description
serverKey string Yes Server key from your BotBye project
url string No Override BotBye API endpoint (default: https://verify.botbye.com)
logger.level string No Log level: "error", "warn", "info", "debug", "log" (default: "info")
logger.logger TLogger No Custom logger instance implementing { error, warn, info, debug, log }
timeouts.evaluate number No Timeout in milliseconds for each evaluate call

Usage

Call evaluate inside the fetch handler where bot protection is needed. Pass an event object describing the request — the SDK returns a decision.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { evaluate } from "@botbye/cloudflare-worker";
// Or if integrating via the Dashboard:
// const { evaluate } = BotBye;

export default {
  async fetch(request, env, ctx) {
    const result = await evaluate({
      type: "validate",
      request: {
        request,
        // "x-botbye-token" is an example — pass the token from wherever you store it
        token: request.headers.get("x-botbye-token"),
      },
    });

    if (result.decision === "BLOCK") {
      return new Response(JSON.stringify({ error: "Forbidden" }), {
        status: 403,
        headers: { "Content-Type": "application/json" },
      });
    }

    // proceed normally
  },
};

There are three event types — validate, risk, and full — each suited for a different layer of your application.

validate — edge-level bot check

Use when you just want to know: was this request made by a bot? Pass the CF Workers Request object directly — the SDK extracts IP, headers, method, and URI automatically.

Event fields:

1
2
3
4
5
6
7
8
9
10
11
12
{
  type: "validate";

  request:
    // Option A: pass the CF Workers Request object directly — SDK extracts everything automatically
    | { request: Request; token?: string | null }
    // Option B: construct request info manually
    | { ip: string; headers: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null };

  customFields?: Record<string, string>;

}

The SDK extracts IP, headers, method, and URI from the CF Workers Request object automatically. You can also pass request info manually — see Option B above. The token is a one-time token generated by the BotBye client-side SDK that contains information about the user's device. Pass whatever the client sent; if no token is received, the decision will be "BLOCK".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { evaluate } from "@botbye/cloudflare-worker";
// Or if integrating via the Dashboard:
// const { evaluate } = BotBye;

export default {
  async fetch(request, env, ctx) {
    const result = await evaluate({
      type: "validate",
      request: {
        request,
        // "x-botbye-token" is an example — pass the token from wherever you store it
        token: request.headers.get("x-botbye-token"),
      },
    });

    if (result.decision === "BLOCK") {
      return new Response(JSON.stringify({ error: "Forbidden" }), {
        status: 403,
        headers: { "Content-Type": "application/json" },
      });
    }

    // proceed normally
  },
};

risk — domain-level risk scoring

Use when you can read user context from the request (e.g. on a login endpoint). Send event.type, event.status, and user.accountId alongside the request to get a domain-level risk score.

Event fields:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  type: "risk";

  request:
    // Only ip is needed at this level
    | { ip: string; headers?: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null }
    // CF Workers Request also accepted if convenient
    | { request: Request };

  event: {
    type: string;   // e.g. "login", "password_change", "checkout"
    status: "ATTEMPTED" | "SUCCESSFUL" | "FAILED" | "UNKNOWN";
  };

  user: {
    accountId: string;
    username?: string | null;
    email?: string | null;
    phone?: string | null;
  };

  customFields?: Record<string, string>;
  botbyeResult?: string; // if a validate call was made earlier, pass its result.botbyeResult here to link the requests; omit if there was no prior validate

}

event and user are the key fields here — they define what action is being performed and who is performing it, which is what drives the risk score. ip is equally important: BotBye tracks which IPs access the account to detect patterns like account sharing, credential stuffing, and suspicious geo logins. Pass it directly as { ip }, or pass the CF Workers Request object if that's more convenient.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { evaluate } from "@botbye/cloudflare-worker";
// Or if integrating via the Dashboard:
// const { evaluate } = BotBye;

export default {
  async fetch(request, env, ctx) {
    const { email } = await request.clone().json();

    const result = await evaluate({
      type: "risk",
      request: { request },
      event: {
        type: "login",
        status: "ATTEMPTED",
      },
      user: {
        accountId: email,
        email,
      },
    });

    if (result.decision === "BLOCK") {
      return new Response(JSON.stringify({ error: "Forbidden" }), {
        status: 403,
        headers: { "Content-Type": "application/json" },
      });
    }

    return fetch(request);
  },
};

Linking validate and risk events

When the same request is evaluated at two layers — for example, once at the edge (type: "validate") and then again inside a domain service (type: "risk") — BotBye can link both events and display them as a single event in the dashboard.

Step 1 — CF Worker (edge): run validate and capture botbye_result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
  async fetch(request, env, ctx) {
    const edgeResult = await evaluate({
      type: "validate",
      request: {
        request,
        // "x-botbye-token" is an example — pass the token from wherever you store it
        token: request.headers.get("x-botbye-token"),
      },
    });
    const edgeBotbyeResult = edgeResult.botbye_result;
    // Pass edgeBotbyeResult downstream — a custom request header, KV, function argument, etc.
  },
};

Step 2 — domain service (auth, payment, account management): pass it as botbyeResult in the risk call:

1
2
3
4
5
6
7
8
9
10
11
12
13
const riskResult = await evaluate({
  type: "risk",
  request: { request },
  event: {
    type: "login",
    status: "ATTEMPTED",
  },
  user: {
    accountId: email,
    email,
  },
  botbyeResult: edgeBotbyeResult,
});

botbye_result is optional in the response — if it is absent, omit botbyeResult and the events will be recorded independently.

full — edge check and domain scoring in one call

Use when the Worker itself is the endpoint and you have all context at once: the raw request with token, the user from the body, and the outcome of the action.

Event fields:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  type: "full";

  request:
    | { request: Request; token?: string | null }
    | { ip: string; headers: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null };

  event: {
    type: string;
    status: "ATTEMPTED" | "SUCCESSFUL" | "FAILED" | "UNKNOWN";
  };

  user: {
    accountId: string;
    username?: string | null;
    email?: string | null;
    phone?: string | null;
  };

  customFields?: Record<string, string>;

}

Equivalent to running validate and risk in a single call.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { evaluate } from "@botbye/cloudflare-worker";
// Or if integrating via the Dashboard:
// const { evaluate } = BotBye;

export default {
  async fetch(request, env, ctx) {
    const { email, password } = await request.json();
    const user = await findUser(email, env);
    const loginSucceeded = user && (await checkPassword(user, password));

    const result = await evaluate({
      type: "full",
      request: {
        request,
        token: request.headers.get("x-botbye-token"),
      },
      event: {
        type: "login",
        status: loginSucceeded ? "SUCCESSFUL" : "FAILED",
      },
      user: {
        accountId: user?.id ?? "",
        email,
      },
    });

    if (result.decision === "BLOCK") {
      return new Response(JSON.stringify({ error: "Forbidden" }), {
        status: 403,
        headers: { "Content-Type": "application/json" },
      });
    }

    // proceed normally
  },
};

Response

evaluate always returns a Promise<TEvaluationResult>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type TEvaluationResult =
  | {
      decision: "ALLOW" | "BLOCK" | "CHALLENGE";
      request_id: string;
      risk_score: number;
      scores: Record<string, number>;
      signals: string[];
      // Present when `decision` is "CHALLENGE": the kind of additional verification to put in
      // front of the visitor.
      challenge?: { type: "CAPTCHA" | "MFA" };
      botbye_result?: string;
      // Present when BotBye received the request but could not fully evaluate it. An integration
      // error (malformed payload, a key resolving to no project) is "BLOCK" with "evaluation_error"
      // in signals; a failure on the BotBye side is "ALLOW".
      error?: { message: string };
    }
  | {
      decision: "ALLOW";
      botbye_result?: string;
      // SDK fail-open: no decision was ever received — request timeout, connection error, or an
      // unparseable response. There is no request_id in this case.
      error: { message: string };
    };

Check result.decision to decide how to handle the request:

  • "ALLOW" — request appears legitimate, proceed normally
  • "BLOCK" — bot or suspicious activity detected, block the request
  • "CHALLENGE" — uncertain, consider issuing a CAPTCHA, MFA, or additional verification step

When the response contains an error field, the request was not fully evaluated:

  • An integration error fails closed with decision: "BLOCK" and signals: ["evaluation_error"]: a malformed payload or invalid JSON, or a key that resolves to no project. error.message names the cause — the offending field, Invalid server key, or Invalid site key for a visit-call. These are surfaced rather than passed silently, so a misconfigured integration cannot sit behind a stream of ALLOW with protection effectively off. Keys are UUIDs, so a wrong one parses cleanly and only fails project lookup — pasting a site key into server-key reaches this case.
  • A BotBye-side failure fails open with decision: "ALLOW": the request was accepted but evaluation could not be completed, and our own fault must not block real users.
  • The SDK fails open with decision: "ALLOW" and no request_id when it never received a decision at all — request timeout, connection error, or an unparseable response.

In every case error.message describes the underlying problem — monitor it and fix the root cause.

Examples of BotBye API responses

Blocked (bot detected):

1
2
3
4
5
6
7
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "BLOCK",
  "risk_score": 0.95,
  "scores": { "bot": 0.95 },
  "signals": ["AutomationTool"]
}

Allowed:

1
2
3
4
5
6
7
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "ALLOW",
  "risk_score": 0.05,
  "scores": { "bot": 0.05, "ato": 0.02 },
  "signals": []
}

Challenge:

1
2
3
4
5
6
7
8
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "CHALLENGE",
  "risk_score": 0.65,
  "scores": { "bot": 0.65 },
  "signals": ["SuspiciousFingerprint"],
  "challenge": { "type": "CAPTCHA" }
}

Invalid server-key — not a known project (fail-closed):

1
2
3
4
5
6
7
8
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "BLOCK",
  "risk_score": 1.0,
  "scores": {},
  "signals": ["evaluation_error"],
  "error": { "message": "Invalid server key" }
}

Evaluation error — request could not be processed (fail-closed):

1
2
3
4
5
6
7
8
{
  "request_id": "f77b2abd-c5d7-44f0-be4f-174b04876583",
  "decision": "BLOCK",
  "risk_score": 1.0,
  "scores": {},
  "signals": ["evaluation_error"],
  "error": { "message": "Invalid request body: missing or invalid field 'user.account_id'" }
}

SDK never received a decision (fail-open):

1
2
3
4
{
  "decision": "ALLOW",
  "error": { "message": "connection error" }
}

What your plan is told

The examples above are what a paid plan receives. risk_score, scores and the detector signals are filled in from the Starter plan up; on Free the verdict is the same and request_id is still there, but those three come back blanked — risk_score is 0.0, scores is {}, and signals keeps only the reasons that name something on your side rather than a detector of ours: token_expected, script_loading_error, token_generation_error, raw_token_changed, and banned_by_rule, the verdict of a rule you wrote yourself.

The protection behaves identically on every plan; only the explanation of a decision is withheld. So branch on decision, never on risk_score — a threshold over the score reads every Free-plan request as low risk.

Advanced: multiple instances

Use factory to create independent SDK instances (useful when protecting multiple projects from one Worker):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { factory } from "@botbye/cloudflare-worker";
// Or if integrating via the Dashboard:
// const { factory } = BotBye;

const sdk = factory();

sdk.init({
  // Use your project server-key
  serverKey: "00000000-0000-0000-0000-000000000000",
});

const result = await sdk.evaluate({
  type: "validate",
  request: { request, token },
});

Dev utilities

1
2
3
4
5
import { dev } from "@botbye/cloudflare-worker";
// Or if integrating via the Dashboard:
// const { dev } = BotBye;

dev.setLoggerLevel("debug"); // "error" | "warn" | "info" | "debug" | "log"