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

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,
        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
type TEvaluationResult =
  | {
      decision: "ALLOW" | "BLOCK" | "CHALLENGE";
      request_id: string;
      risk_score: number;
      scores: Record<string, number>;
      signals: string[];
      botbye_result?: string;
      // Present when the backend received the request but could not process it (e.g. a malformed
      // payload or invalid JSON — such as a client omitting required fields): decision is "BLOCK"
      // and signals contain "evaluation_error".
      error?: { message: string };
    }
  | {
      decision: "ALLOW";
      botbye_result?: string;
      // SDK fail-open: connectivity or configuration error (e.g. invalid server key).
      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, BotBye could not fully evaluate the request:

  • The SDK fails open with decision: "ALLOW" on connectivity or configuration problems (e.g. an invalid server-key), so a misconfiguration does not block real users.
  • The backend fails closed with decision: "BLOCK" and signals: ["evaluation_error"] when it receives a request it cannot process — a malformed payload or invalid JSON, such as a client omitting required fields. The error.message names the offending field.

In both cases 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 (SDK fail-open):

1
2
3
4
{
  "decision": "ALLOW",
  "error": { "message": "[BotBye] Bad Request: Invalid Server Key" }
}

Evaluation error — backend could not process the request (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'" }
}

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"