{"_id":"@botbye/nest-express","_rev":"3-021e4e9253912f59b4c52bc4dda043ab","name":"@botbye/nest-express","dist-tags":{"latest":"2.2.0"},"versions":{"2.0.0":{"name":"@botbye/nest-express","version":"2.0.0","license":"MIT","_id":"@botbye/nest-express@2.0.0","maintainers":[{"name":"botbye","email":"accounts@botbye.com"}],"homepage":"https://botbye.com","bugs":{"url":"https://github.com/botbye/botbye-nest-express/issues"},"dist":{"shasum":"0c7172dc40ce2730fec9985b049ccf23aa5b159e","tarball":"https://registry.npmjs.org/@botbye/nest-express/-/nest-express-2.0.0.tgz","fileCount":11,"integrity":"sha512-wSPsMdhhQXbomId9kAcRyo7bVUXiCAjbmj/AubaJxZVY+KWqiZmWBSWKoFGZGkQmJ+H8edWtkMcJB3XuNZ6DYA==","signatures":[{"sig":"MEYCIQCrY24RgWnRIPglQfWFR+yppal54wDs3Ti7vZ9LBcGqBAIhALAij5EsZ8tSsFMUjYV+A1wzTA3lr+dra2Ni0fKw1Iwx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":18402},"main":"src/index.js","types":"src/index.d.ts","exports":{".":{"types":"./src/index.d.ts","import":"./src/index.js","require":"./src/index.js"}},"gitHead":"da318ecc5dd07485c1727eed8554dd5e68459fb8","_npmUser":{"name":"botbye","email":"accounts@botbye.com"},"repository":{"url":"git+https://github.com/botbye/botbye-nest-express.git","type":"git"},"_npmVersion":"10.8.3","description":"BotBye! integration for NestJS with Express","directories":{},"_nodeVersion":"22.9.0","dependencies":{"@botbye/express":"^2.0.1","@botbye/nest-core":"^2.0.0","@botbye/node-core":"^2.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"express":">=4","@nestjs/common":">=10"},"_npmOperationalInternal":{"tmp":"tmp/nest-express_2.0.0_1779295458632_0.9238862696687722","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@botbye/nest-express","version":"2.1.0","license":"MIT","_id":"@botbye/nest-express@2.1.0","maintainers":[{"name":"botbye","email":"accounts@botbye.com"}],"homepage":"https://botbye.com","bugs":{"url":"https://github.com/botbye/botbye-nest-express/issues"},"dist":{"shasum":"a4e17728f352cbcbb0904146eeb0a4fb1b9a50b8","tarball":"https://registry.npmjs.org/@botbye/nest-express/-/nest-express-2.1.0.tgz","fileCount":11,"integrity":"sha512-IwlpQfEx77AIenspJgWTQ1j6pv+POoZpjHyciyde/baCPGWfCfA4IoTvv4ic4hMou1TmT5MvApd/HFlzNnYtpQ==","signatures":[{"sig":"MEYCIQDsBHXpnDQPpeolPEq3pJXU6bLwx+8gASV8fFnxI9KjxgIhALYTgW6Zv9YlVXgct2Y+/tM096MIrSdakD+9HeZ7BbUq","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19423},"main":"src/index.js","types":"src/index.d.ts","exports":{".":{"types":"./src/index.d.ts","import":"./src/index.js","require":"./src/index.js"}},"gitHead":"b7d185f815f69e117db907b95fc0847476656d49","_npmUser":{"name":"botbye","email":"accounts@botbye.com"},"repository":{"url":"git+https://github.com/botbye/botbye-nest-express.git","type":"git"},"_npmVersion":"10.8.3","description":"BotBye! integration for NestJS with Express","directories":{},"_nodeVersion":"22.9.0","dependencies":{"@botbye/express":"^2.1.0","@botbye/nest-core":"^2.1.0","@botbye/node-core":"^2.1.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"express":">=4","@nestjs/common":">=10"},"_npmOperationalInternal":{"tmp":"tmp/nest-express_2.1.0_1779744018224_0.5793895100048527","host":"s3://npm-registry-packages-npm-production"}},"2.2.0":{"name":"@botbye/nest-express","version":"2.2.0","description":"BotBye! integration for NestJS with Express","main":"src/index.js","types":"src/index.d.ts","exports":{".":{"import":"./src/index.js","require":"./src/index.js","types":"./src/index.d.ts"}},"license":"MIT","publishConfig":{"access":"public"},"homepage":"https://botbye.com","repository":{"type":"git","url":"git+https://github.com/botbye/botbye-nest-express.git"},"dependencies":{"@botbye/node-core":"^2.2.0","@botbye/nest-core":"^2.2.0","@botbye/express":"^2.2.0"},"peerDependencies":{"@nestjs/common":">=10","express":">=4"},"_id":"@botbye/nest-express@2.2.0","gitHead":"39f44ed0b9843572888098ac94f9629d393aaac3","bugs":{"url":"https://github.com/botbye/botbye-nest-express/issues"},"_nodeVersion":"22.19.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-6p+aQha4Y1IWV6dCWK721pNqVYxqKJY8kHkhCBhf7kDVgtArGCddeAyAc3hNSQZrDPPJhSMjBw+I02TMi8NsmQ==","shasum":"0e14d62a92ba00d9ada6974b8311437432e9ff7e","tarball":"https://registry.npmjs.org/@botbye/nest-express/-/nest-express-2.2.0.tgz","fileCount":15,"unpackedSize":26591,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHBSBJlSe5vrHNiSIe9nzhMwDlWWmg4+0p+ihyz5ERP2AiEApV1OwPZ31ZqpkR5damkCOhQgCfq+JO74bZTApzqc96o="}]},"_npmUser":{"name":"botbye","email":"accounts@botbye.com"},"directories":{},"maintainers":[{"name":"botbye","email":"accounts@botbye.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nest-express_2.2.0_1784212349254_0.8954032898714734"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-20T16:44:18.537Z","modified":"2026-07-16T14:32:29.618Z","2.0.0":"2026-05-20T16:44:18.779Z","2.1.0":"2026-05-25T21:20:18.376Z","2.2.0":"2026-07-16T14:32:29.384Z"},"bugs":{"url":"https://github.com/botbye/botbye-nest-express/issues"},"license":"MIT","homepage":"https://botbye.com","repository":{"type":"git","url":"git+https://github.com/botbye/botbye-nest-express.git"},"description":"BotBye! integration for NestJS with Express","maintainers":[{"name":"botbye","email":"accounts@botbye.com"}],"readme":"# @botbye/nest-express\n\n[BotBye!](https://botbye.com) integration for [NestJS](https://nestjs.com/) with [Express](https://expressjs.com/) applications.\n\nFull documentation: https://botbye.com/docs/server-side/node-js/nestjs/express\n\n## Install\n\n```bash\nnpm i @botbye/nest-express\n```\n\n```bash\nyarn add @botbye/nest-express\n```\n\nRequires `@nestjs/common >= 10` and `express >= 4` as peer dependencies.\n\n## Configuration\n\nRegister `BotByeModule` once in your root module:\n\n```typescript\nimport { Module } from \"@nestjs/common\";\nimport { BotByeModule } from \"@botbye/nest-express\";\n\n@Module({\n  imports: [\n    BotByeModule.register({\n      // Use your project server-key\n      serverKey: \"00000000-0000-0000-0000-000000000000\",\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### `init` options\n\n| Option | Type | Required | Description |\n|---|---|---|---|\n| `serverKey` | `string` | Yes | Server key from your BotBye project |\n| `url` | `string` | No | Override BotBye API endpoint (default: `https://verify.botbye.com`) |\n| `logger.level` | `\"error\" \\| \"warn\" \\| \"info\" \\| \"debug\" \\| \"log\"` | No | Log level (default: `\"info\"`) |\n| `logger.logger` | `TLogger` | No | Custom logger instance implementing `{ error, warn, info, debug, log }` |\n| `timeouts.evaluate` | `number` | No | Timeout in milliseconds for each `evaluate` call |\n| `tokenExtractor` | `(request: Request) => string \\| null \\| undefined` | No* | Extracts the BotBye token from the Express request. *Required when using `BotByeMiddleware` — without it the middleware skips evaluation and returns an error result. |\n\n## Usage\n\nAfter registering `BotByeModule`, inject `TBotByeService` using `BOTBYE_SERVICE_DI_TOKEN` to call `evaluate` from guards, controllers, or services.\n\n```typescript\nimport { Controller, Post, Inject, Req, ForbiddenException } from \"@nestjs/common\";\nimport { Request } from \"express\";\nimport { BOTBYE_SERVICE_DI_TOKEN, TBotByeService } from \"@botbye/nest-express\";\n\n@Controller()\nexport class AppController {\n  constructor(\n    @Inject(BOTBYE_SERVICE_DI_TOKEN) private readonly botbye: TBotByeService\n  ) {}\n\n  @Post(\"/api/submit\")\n  async submit(@Req() req: Request) {\n    const result = await this.botbye.evaluate({\n      type: \"validate\",\n      request: {\n        request: req,\n        // \"x-botbye-token\" is an example — pass the token from wherever you store it\n        token: req.headers[\"x-botbye-token\"] as string | null,\n      },\n    });\n\n    if (result.decision === \"BLOCK\") {\n      throw new ForbiddenException();\n    }\n\n    // proceed normally\n  }\n}\n```\n\nThere are three event types — `validate`, `risk`, and `full` — each suited for a different layer of your application.\n\n---\n\n### `validate` — edge-level bot check\n\nUse at the edge — guard, route handler, middleware — when you just want to know: **was this request made by a bot?** No user or domain context needed.\n\n**Event fields:**\n\n```typescript\n{\n  type: \"validate\";\n\n  request:\n    // Option A: pass the Express request object directly — SDK extracts everything automatically\n    | { request: Request; token?: string | null }\n    // Option B: construct request info manually\n    | { ip: string; headers: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null };\n\n  customFields?: Record<string, string>;\n\n}\n```\n\nThe SDK extracts IP, headers, method, and URI from the Express request 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](https://botbye.com/docs/client-side/npm-module) that contains information about the user's device. Pass whatever the client sent; if no token is received, the decision will be `\"BLOCK\"`.\n\n```typescript\nimport { Injectable, CanActivate, ExecutionContext, Inject } from \"@nestjs/common\";\nimport { Request } from \"express\";\nimport { BOTBYE_SERVICE_DI_TOKEN, TBotByeService } from \"@botbye/nest-express\";\n\n@Injectable()\nexport class BotProtectionGuard implements CanActivate {\n  constructor(\n    @Inject(BOTBYE_SERVICE_DI_TOKEN) private readonly botbye: TBotByeService\n  ) {}\n\n  async canActivate(context: ExecutionContext): Promise<boolean> {\n    const request = context.switchToHttp().getRequest<Request>();\n\n    const result = await this.botbye.evaluate({\n      type: \"validate\",\n      request: {\n        request,\n        // \"x-botbye-token\" is an example — pass the token from wherever you store it\n        token: request.headers[\"x-botbye-token\"] as string | null,\n      },\n    });\n\n    return result.decision !== \"BLOCK\";\n  }\n}\n```\n\n---\n\n### `risk` — domain-level risk scoring\n\nUse inside services that already know the user: auth, payments, account management, etc. The purpose shifts from \"is this a bot?\" to **\"is something suspicious happening for this user?\"** — credential stuffing, account takeover, account sharing, logins from a new geo.\n\n**Event fields:**\n\n```typescript\n{\n  type: \"risk\";\n\n  request:\n    // Only ip is needed at this level\n    | { ip: string; headers?: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null }\n    // Express request object also accepted if convenient\n    | { request: Request };\n\n  event: {\n    type: string;   // e.g. \"login\", \"password_change\", \"checkout\"\n    status: \"ATTEMPTED\" | \"SUCCESSFUL\" | \"FAILED\" | \"UNKNOWN\";\n  };\n\n  user: {\n    accountId: string;\n    username?: string | null;\n    email?: string | null;\n    phone?: string | null;\n  };\n\n  customFields?: Record<string, string>;\n  botbyeResult?: string;\n\n}\n```\n\n`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 Express request object if that's more convenient.\n\n```typescript\nimport { Injectable, Inject } from \"@nestjs/common\";\nimport { BOTBYE_SERVICE_DI_TOKEN, TBotByeService } from \"@botbye/nest-express\";\n\n@Injectable()\nexport class AuthService {\n  constructor(\n    @Inject(BOTBYE_SERVICE_DI_TOKEN) private readonly botbye: TBotByeService\n  ) {}\n\n  async onLoginAttempt(ip: string, userId: string, email: string, loginSucceeded: boolean) {\n    const result = await this.botbye.evaluate({\n      type: \"risk\",\n      request: { ip },\n      event: {\n        type: \"login\",\n        status: loginSucceeded ? \"SUCCESSFUL\" : \"FAILED\",\n        // \"SUCCESSFUL\" | \"FAILED\" | \"ATTEMPTED\" | \"UNKNOWN\"\n      },\n      user: {\n        accountId: userId,\n        email,\n      },\n    });\n\n    if (result.decision === \"BLOCK\") {\n      // Lock account, trigger MFA, send alert, etc.\n    }\n  }\n}\n```\n\n#### Linking `validate` and `risk` events\n\nWhen the same request is evaluated at two layers — for example, once at the edge in a guard (`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.\n\n**Step 1 — guard** (edge layer): run `validate` and capture `botbye_result`:\n\n```typescript\n// e.g. in BotProtectionGuard.canActivate()\nconst edgeResult = await this.botbye.evaluate({\n  type: \"validate\",\n  request: {\n    request,\n    // \"x-botbye-token\" is an example — pass the token from wherever you store it\n    token: request.headers[\"x-botbye-token\"] as string | null,\n  },\n});\nconst edgeBotbyeResult = edgeResult.botbye_result;\n// Pass edgeBotbyeResult downstream — request object, function argument, shared context, etc.\n```\n\n**Step 2 — domain service** (auth, payment, account management): pass it as `botbyeResult` in the `risk` call:\n\n```typescript\n// e.g. in AuthService.onLoginAttempt()\nconst riskResult = await this.botbye.evaluate({\n  type: \"risk\",\n  request: { ip },\n  event: {\n    type: \"login\",\n    status: loginSucceeded ? \"SUCCESSFUL\" : \"FAILED\",\n  },\n  user: {\n    accountId: userId,\n    email,\n  },\n  botbyeResult: edgeBotbyeResult,\n});\n```\n\n`botbye_result` is optional in the response — if it is absent, omit `botbyeResult` and the events will be recorded independently.\n\n---\n\n### `full` — edge check and domain scoring in one call\n\nUse when you have all context at once: raw request, token, user, and event. Equivalent to running `validate` and `risk` in a single call. A login endpoint is a typical example — it receives the HTTP request and immediately knows the user and outcome.\n\n**Event fields:**\n\n```typescript\n{\n  type: \"full\";\n\n  request:\n    | { request: Request; token?: string | null }\n    | { ip: string; headers: Record<string, string>; requestMethod?: string | null; requestUri?: string | null; token?: string | null };\n\n  event: {\n    type: string;\n    status: \"ATTEMPTED\" | \"SUCCESSFUL\" | \"FAILED\" | \"UNKNOWN\";\n  };\n\n  user: {\n    accountId: string;\n    username?: string | null;\n    email?: string | null;\n    phone?: string | null;\n  };\n\n  customFields?: Record<string, string>;\n\n}\n```\n\n```typescript\nimport { Injectable, Inject, ForbiddenException } from \"@nestjs/common\";\nimport { Request } from \"express\";\nimport { BOTBYE_SERVICE_DI_TOKEN, TBotByeService } from \"@botbye/nest-express\";\n\n@Injectable()\nexport class AuthService {\n  constructor(\n    @Inject(BOTBYE_SERVICE_DI_TOKEN) private readonly botbye: TBotByeService\n  ) {}\n\n  async login(request: Request, email: string, password: string) {\n    const user = await this.findUser(email);\n    const loginSucceeded = user && (await this.checkPassword(user, password));\n\n    const result = await this.botbye.evaluate({\n      type: \"full\",\n      request: {\n        request,\n        // \"x-botbye-token\" is an example — pass the token from wherever you store it\n        token: request.headers[\"x-botbye-token\"] as string | null,\n      },\n      event: {\n        type: \"login\",\n        status: loginSucceeded ? \"SUCCESSFUL\" : \"FAILED\",\n      },\n      user: {\n        accountId: user?.id ?? \"unknown\",\n        email,\n      },\n    });\n\n    if (result.decision === \"BLOCK\") {\n      throw new ForbiddenException();\n    }\n\n    // proceed normally\n  }\n}\n```\n\n---\n\n## Response\n\n`evaluate` always returns a `Promise<TEvaluationResult>`:\n\n```typescript\ntype TEvaluationResult =\n  | {\n      decision: \"ALLOW\" | \"BLOCK\" | \"CHALLENGE\";\n      request_id: string;\n      risk_score: number;\n      scores: Record<string, number>;\n      signals: string[];\n      botbye_result?: string;\n    }\n  | {\n      decision: \"ALLOW\" | \"BLOCK\" | \"CHALLENGE\";\n      botbye_result?: string;\n      error: { message: string };\n    };\n```\n\nCheck `result.decision` to decide how to handle the request:\n\n- `\"ALLOW\"` — request appears legitimate, proceed normally\n- `\"BLOCK\"` — bot or suspicious activity detected, block the request\n- `\"CHALLENGE\"` — uncertain, consider issuing a CAPTCHA, MFA, or additional verification step\n\nWhen the response contains an `error` field, BotBye could not evaluate the request (e.g. invalid server key). In that case `decision` defaults to `\"ALLOW\"` so that a misconfiguration does not block real users — but you should monitor and fix the underlying error.\n\n### Response examples\n\nBlocked (bot detected):\n\n```json\n{\n  \"request_id\": \"f77b2abd-c5d7-44f0-be4f-174b04876583\",\n  \"decision\": \"BLOCK\",\n  \"risk_score\": 0.95,\n  \"scores\": { \"bot\": 0.95 },\n  \"signals\": [\"AutomationTool\"]\n}\n```\n\nAllowed:\n\n```json\n{\n  \"request_id\": \"f77b2abd-c5d7-44f0-be4f-174b04876583\",\n  \"decision\": \"ALLOW\",\n  \"risk_score\": 0.05,\n  \"scores\": { \"bot\": 0.05, \"ato\": 0.02 },\n  \"signals\": []\n}\n```\n\nChallenge:\n\n```json\n{\n  \"request_id\": \"f77b2abd-c5d7-44f0-be4f-174b04876583\",\n  \"decision\": \"CHALLENGE\",\n  \"risk_score\": 0.65,\n  \"scores\": { \"bot\": 0.65 },\n  \"signals\": [\"SuspiciousFingerprint\"],\n  \"challenge\": { \"type\": \"CAPTCHA\", \"token\": \"...\" }\n}\n```\n\nInvalid `serverKey`:\n\n```json\n{\n  \"decision\": \"ALLOW\",\n  \"error\": { \"message\": \"[BotBye] Bad Request: Invalid Server Key\" }\n}\n```\n\n## Middleware usage\n\n`BotByeMiddleware` evaluates every incoming request automatically and stores the result on the request object. Use the `@BotByeResponse()` parameter decorator to access the result in a controller without calling `evaluate` manually.\n\nProvide a `tokenExtractor` in module options so the middleware knows where to find the BotBye token:\n\n```typescript\n// app.module.ts\nimport { Module, NestModule, MiddlewareConsumer } from \"@nestjs/common\";\nimport { BotByeModule, BotByeMiddleware } from \"@botbye/nest-express\";\n\n@Module({\n  imports: [\n    BotByeModule.register({\n      // Use your project server-key\n      serverKey: \"00000000-0000-0000-0000-000000000000\",\n      // \"x-botbye-token\" is an example — pass the token from wherever you store it\n      tokenExtractor: (req) => req.headers[\"x-botbye-token\"] as string | undefined,\n    }),\n  ],\n})\nexport class AppModule implements NestModule {\n  configure(consumer: MiddlewareConsumer) {\n    // Global: protect all routes\n    consumer.apply(BotByeMiddleware).forRoutes(\"*\");\n  }\n}\n```\n\nAccess the result in a controller with `@BotByeResponse()`:\n\n```typescript\n// app.controller.ts\nimport { Controller, Post, ForbiddenException } from \"@nestjs/common\";\nimport { BotByeResponse } from \"@botbye/nest-express\";\nimport type { TEvaluationResult } from \"@botbye/nest-express\";\n\n@Controller()\nexport class AppController {\n  @Post(\"/api/submit\")\n  async submit(@BotByeResponse() result: TEvaluationResult) {\n    if (result.decision === \"BLOCK\") {\n      throw new ForbiddenException();\n    }\n    // proceed normally\n  }\n}\n```\n\nTo apply middleware only to specific routes, replace `.forRoutes(\"*\")` with a path or route descriptor:\n\n```typescript\n// Scoped: protect only routes registered under \"protected\"\nconsumer.apply(BotByeMiddleware).forRoutes(\"protected\");\n```\n\n## Anti-Phishing\n\nBotBye's anti-phishing detects look-alike sites that clone your pages to steal credentials. This integration serves the detection **catcher** from your own origin, so the BotBye domain stays hidden from the client.\n\n- [Anti-Phishing Protection overview](https://botbye.com/docs/anti-phishing/overview)\n- [Why a server-side integration is needed](https://botbye.com/docs/anti-phishing/overview#server-integration)\n\nAnti-phishing is identified by its own `clientKey` (available in your Phishing Project in the Dashboard), not the server key used by `evaluate`, so it is configured separately.\n\n### Init\n\nRegister `BotByePhishingModule` alongside `BotByeModule` in your root module:\n\n```typescript\nimport { Module } from \"@nestjs/common\";\nimport { BotByePhishingModule } from \"@botbye/nest-express\";\n\n@Module({\n  imports: [\n    BotByePhishingModule.register({\n      // clientKey from your Phishing Project on the Admin Dashboard\n      clientKey: \"00000000-0000-0000-0000-000000000000\",\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### `register` options\n\n| Option | Type | Required | Description |\n|---|---|---|---|\n| `clientKey` | `string` | Yes | `clientKey` from your Phishing Project on the Admin Dashboard |\n| `url` | `string` | No | Override BotBye API endpoint (default: `https://verify.botbye.com`) |\n| `logger.level` | `\"error\" \\| \"warn\" \\| \"info\" \\| \"debug\" \\| \"log\"` | No | Log level (default: `\"info\"`) |\n| `logger.logger` | `TLogger` | No | Custom logger instance implementing `{ error, warn, info, debug, log }` |\n| `timeouts.fetchCatcher` | `number` | No | Timeout in milliseconds for each `fetchCatcher` call |\n\n### Usage\n\nAnti-phishing needs **two routes on your own origin**, each proxied through `fetchCatcher`:\n\n- **SVG route** — serves the SVG catcher. This is the URL your [client](https://botbye.com/docs/anti-phishing/integrations/client/js-tag#getcatcher-options) passes to `getCatcher({ url })`.\n- **PNG route** — serves the PNG that the SVG references (via `innerPngUrl`).\n\nAfter registering `BotByePhishingModule`, inject `TBotByePhishingService` using `BOTBYE_PHISHING_SERVICE_DI_TOKEN` and call `fetchCatcher` from a controller. Pass the Express request as `request` and the `format`. For the SVG, `innerPngUrl` must be the **absolute URL** of your PNG route — the browser loads that PNG directly from your origin.\n\n```typescript\nimport { Controller, Get, Inject, Req, Res } from \"@nestjs/common\";\nimport { Request, Response } from \"express\";\nimport { BOTBYE_PHISHING_SERVICE_DI_TOKEN, TBotByePhishingService } from \"@botbye/nest-express\";\n\n// Absolute URL of your PNG endpoint — the SVG catcher references it through innerPngUrl.\nconst PNG_CATCHER_URL = \"https://your-site.example/botbye-catcher.png\";\n\n@Controller()\nexport class PhishingCatcherController {\n  constructor(\n    @Inject(BOTBYE_PHISHING_SERVICE_DI_TOKEN) private readonly phishing: TBotByePhishingService\n  ) {}\n\n  // Endpoint 1 — SVG catcher (this URL goes into the catcher element on your pages)\n  @Get(\"/botbye-catcher.svg\")\n  async svg(@Req() request: Request, @Res() res: Response) {\n    const catcher = await this.phishing.fetchCatcher({\n      request,\n      format: \"svg\",\n      innerPngUrl: PNG_CATCHER_URL, // absolute URL of the PNG endpoint below\n    });\n\n    res.status(catcher.status).set(catcher.headers).send(Buffer.from(catcher.body));\n  }\n\n  // Endpoint 2 — companion PNG the SVG above references via innerPngUrl (PNG_CATCHER_URL)\n  @Get(\"/botbye-catcher.png\")\n  async png(@Req() request: Request, @Res() res: Response) {\n    const catcher = await this.phishing.fetchCatcher({ request, format: \"png\" });\n\n    res.status(catcher.status).set(catcher.headers).send(Buffer.from(catcher.body));\n  }\n}\n```\n\nWe recommend embedding the **SVG catcher**: it is designed to keep tracking even when a phishing site copies all of your assets to its own infrastructure (the PNG route exists because the SVG catcher relies on it).\n\n## Documentation\n\n- Web: https://botbye.com/docs/server-side/node-js/nestjs/express\n- Markdown (for AI tools and agents): https://botbye.com/docs/server-side/node-js/nestjs/express.md\n","readmeFilename":"README.md"}