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"