Spring (Kotlin)

Spring (Kotlin)

Installation

Add the dependency:

1
2
3
4
5
<dependency>
    <groupId>com.botbye</groupId>
    <artifactId>kotlin-module</artifactId>
    <version>4.0.0</version>
</dependency>
1
implementation("com.botbye:kotlin-module:4.0.0")

Configuration

Expose the dedicated BotbyePhishingClient as a Spring bean. Phishing is separate from the evaluate Botbye client and is identified by a public, browser-safe clientKey, so it needs no server key. On construction it makes a one-off, best-effort server-integration init handshake that reports this server-side integration to BotBye; it is non-blocking, so expose it as a singleton bean and reuse it.

BotbyePhishingConfig takes 2 values: endpoint (optional, defaults to https://verify.botbye.com) and clientKey.

Getting clientKey

clientKey is the public, browser-safe identifier of your phishing project. It travels in the asset URL path, so it is safe to expose — no secret token and no Base64 encoding are required.

Find it on the Get Started screen of your phishing project in the BotBye dashboard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.botbye.phishing.BotbyePhishingCatcher
import com.botbye.phishing.BotbyePhishingClient
import com.botbye.phishing.BotbyePhishingConfig
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class AppConfig {

    @Bean
    fun phishingClient(): BotbyePhishingClient<Nothing> =
        BotbyePhishingClient(
            BotbyePhishingConfig(
                endpoint = "https://verify.botbye.com",
                clientKey = "<public-client-key>",
            )
        )
}

Usage

Inject the client into a controller. fetchCatcher is a suspend function, so the controller methods are suspend too.

Anti-phishing needs two routes on your own origin: an SVG route — the URL your client code passes to getCatcher({ url }) — and a PNG route that the SVG references. The paths are arbitrary, so name them like ordinary static assets and let the route decide the format. A path that spells out the vendor or the feature (/api/phishing/…) is what a copied page is searched for and stripped of, and a format query param on the pixel URL reads the same way.

On the SVG route, pass innerPngUrl — the absolute URL of your PNG route: the returned SVG embeds it as its tracking pixel. It is required: the SVG catcher takes it as a constructor argument, so an SVG asset without one does not compile, and a blank one is rejected on the spot rather than reaching the wire. Build that URL from your own host — image_id is owned by the SDK and is not read from the forwarded query. skipExecution defaults to true, the script-less SVG; pass false only for browsers predating crossorigin on svg <image> (Chrome 118, Firefox 114, Safari 17.2), where the script-driven variant is the one that still reports.

These examples forward no query: format, image_id and executable are set by the call itself, and only module_name / module_version pass through from the browser's pixel query — which a catcher mounted on your own routes never receives.

If you would rather not read the request yourself, bind it once: BotbyePhishingClient.withExtractor(config, BotbyePhishingRequestExtractor { request -> BotbyePhishingRequestInfo(origin = …, referer = …, query = …) }) gives a BotbyePhishingClient<YourRequest> whose fetchCatcher(request, BotbyePhishingCatcher.Svg(…)) takes the raw request instead of origin / referer. The extractor is then the only thing that reads the request — headers and query alike.

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
37
38
39
40
41
42
43
@RestController
class PhishingController(
    private val phishing: BotbyePhishingClient<Nothing>,
) {

    @GetMapping("/your-image-route.svg")
    suspend fun svg(
        @RequestHeader(name = "Origin", required = false) origin: String?,
        @RequestHeader(name = "Referer", required = false) referer: String?,
    ): ResponseEntity<ByteArray> = relay(
        phishing.fetchCatcher(
            BotbyePhishingCatcher.Svg(PNG_CATCHER_URL),
            origin = origin,
            referer = referer,
        )
    )

    @GetMapping("/your-image-route.png")
    suspend fun png(
        @RequestHeader(name = "Origin", required = false) origin: String?,
        @RequestHeader(name = "Referer", required = false) referer: String?,
    ): ResponseEntity<ByteArray> = relay(
        phishing.fetchCatcher(BotbyePhishingCatcher.Png, origin = origin, referer = referer)
    )

    private fun relay(response: BotbyePhishingResponse): ResponseEntity<ByteArray> {
        if (response.error != null) {
            return ResponseEntity.status(502).build()
        }

        val contentType = response.headers["Content-Type"] ?: "image/png"

        return ResponseEntity
            .status(response.status)
            .contentType(MediaType.valueOf(contentType))
            .body(response.body)
    }

    private companion object {
        // Absolute URL of your PNG route — the SVG catcher references it through innerPngUrl.
        const val PNG_CATCHER_URL = "https://your-site.example/your-image-route.png"
    }
}

Settings

Configuration parameters for phishing integration:

Setting Description Required Default Value
endpoint Host of the phishing API no https://verify.botbye.com
clientKey Public client-key of your phishing project yes -