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"