v1.0.0
Kotlin KMP Android · iOS Ktor Plugin
Ferret mascot
Get Started

Introduction

Ferret is a Kotlin Multiplatform network inspector that plugs directly into your Ktor HttpClient. It captures every HTTP request, response, and WebSocket message — and surfaces it all in a slick on-device debug UI, with no proxy, no certificate, no separate build.

What does Ferret do?

Ferret acts as a transparent Ktor plugin. Every request and response flows through it, gets persisted to a local Room (SQLite) database, and is immediately visible via a rolling system notification and a full in-app inspector screen — all entirely on-device.

How it works

  • 1

    Install the plugin

    Call install(Ferret) { ... } when you create your HttpClient. That's all the code you write.

  • 2

    Traffic is captured automatically

    Every HTTP request, response, and WebSocket frame that flows through your client is intercepted and stored locally. No proxy, no certificate trust profile, no separate debug build variant required.

  • 3

    Inspect from anywhere

    A rolling system notification summarizes recent activity — tap it to jump straight into the inspector. On Android a home-screen shortcut gives one-tap access even when the notification is dismissed.

Supported platforms

Platform Ktor Engine Status
Android OkHttp ✅ Fully supported
iOS Darwin ✅ Fully supported

Distributed via Maven Central

Ferret is published to Maven Central under the group io.github.ferret-org. See Basic Integration for the dependency snippet.

Get Started

Features

Everything you need to inspect your app's network traffic — without leaving the device.

📡
HTTP & WebSocket
Captures regular HTTP requests/responses and WebSocket connections with all their frames — sent and received.
📱
Kotlin Multiplatform
One dependency, one integration step. Identical behavior on Android and iOS — no platform-specific code to write.
🔔
Rolling Notification
A persistent notification summarizes the most recent requests. Tap it at any time to open the full inspector.
App Shortcut
Android launcher shortcut for one-tap access to the inspector from the home screen — no notification needed.
🔍
Search & Filter
Filter the record list by URL, HTTP method, status code, or by traffic type (HTTP / WebSocket / All).
📤
Share as cURL
Export any request as a ready-to-run curl command or as plain text for a bug report — one tap.
📋
Copy to Clipboard
Tap any field — URL, header, body, status — to copy it to the clipboard directly from the detail screen.
🔒
Fully On-Device
All captured data lives in a local Room database. Nothing is ever sent to an external server or logged outside the app.
No proxy needed

Unlike Charles Proxy or Proxyman, Ferret requires no SSL certificate trust profile, no Wi-Fi proxy configuration, and no desktop companion. It works inside the app process, on the device, in any environment.

Get Started

Requirements

Ferret requires Ktor as your HTTP client. Here are the full version requirements before adding the dependency.

Tool / Dependency Minimum Built & tested with
Kotlin 1.9.0 2.4.0
Ktor 3.x 3.5.1
Compose Multiplatform 1.10.x 1.10.3
Android Gradle Plugin 9.2.1
Gradle 9.4.1
Android minSdk 28 28
Android compileSdk / targetSdk 37 37
minSdk 28 is a hard floor

Ferret declares minSdk 28 in its own manifest. Any app that adds Ferret as a dependency inherits this constraint. Check your app's minSdk before integrating.

Ktor is required

Ferret is implemented as a Ktor HttpClientPlugin. It is not a system proxy or a network VPN — your app must route HTTP traffic through a Ktor HttpClient for Ferret to intercept it.

iOS targets

Ferret publishes pre-built artifacts for all three standard iOS targets:

  • iosArm64 — physical iPhone / iPad devices
  • iosSimulatorArm64 — Apple Silicon simulators
  • iosX64 — Intel Mac simulators
Integration

Basic Integration

Add the dependency from Maven Central and install the Ktor plugin — that's all it takes.

Step 1 — Version catalog

Declare the version and library alias in gradle/libs.versions.toml:

libs.versions.toml TOML
[versions]
ferret = "1.0.0"

[libraries]
ferret = { module = "io.github.ferret-org:ferret", version.ref = "ferret" }

Step 2 — Add the dependency

In your shared/common module's build.gradle.kts:

build.gradle.kts Kotlin DSL
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.ferret)
        }
    }
}
Next step

After adding the dependency, follow the platform-specific setup to install the plugin: Android Setup or iOS Setup.

Integration

Android Setup

Full setup guide for Android: manifest permissions, client configuration, and what Ferret configures automatically on first run.

1 · Add manifest permissions

Add both permissions to your app's AndroidManifest.xml:

AndroidManifest.xml XML
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
POST_NOTIFICATIONS

This permission is needed to display the rolling capture notification. Ferret still captures all traffic without it — the notification just won't appear. See Notification Permission for how to request it at runtime on Android 13+.

2 · Install the plugin

NetworkModule.kt Kotlin
import com.ferret.intercept.Ferret
import com.ferret.intercept.install
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*

val client = HttpClient(OkHttp) {
    install(Ferret) {
        context = applicationContext
    }
}

Pass an Android Context to the context property. Using applicationContext is recommended — it survives configuration changes and Activity recreation.

3 · What Ferret configures automatically

The first time the plugin initializes it performs four setup steps automatically:

  1. Creates its Room database on-device to persist captured network records.
  2. Creates a notification channel ("General" by default) on Android 8+.
  3. Registers a dynamic launcher shortcut labeled "Open Ferret Inspector".
  4. Starts posting the rolling notification as traffic flows through the client.

Activity auto-merge

Ferret's inspector activity (com.ferret.ui.FerretActivity) is declared in the library's own manifest and merges into your app automatically at build time. No manual declaration in your manifest is needed.

Early access

If the inspector is opened before the plugin has been installed (e.g. via the shortcut before the app runs), Ferret shows a dialog explaining it isn't ready yet — it does not crash.

Integration

iOS Setup

Ferret works on iOS with minimal setup. No context property, no manifest changes — install the plugin and decide how to present the inspector.

Install the plugin

NetworkModule.kt Kotlin
import com.ferret.intercept.Ferret
import com.ferret.intercept.install
import io.ktor.client.*
import io.ktor.client.engine.darwin.*

val client = HttpClient(Darwin) {
    install(Ferret) {}
}

There is no context field on iOS. Ferret initializes automatically the first time the plugin is installed.

Presenting the inspector

iOS has no equivalent of Android's home-screen app shortcuts, so you decide where and how to surface the inspector. Common approaches:

  • A debug section in your app's settings screen
  • A shake gesture (override motionEnded in your root view controller)
  • A hidden tap sequence on a logo or version label
  • A long-press on a designated debug element

To open the inspector, call FerretIosUi.open() from Kotlin or present ferretViewController() from Swift:

DebugHelper.kt Kotlin
FerretIosUi.open()
DebugViewController.swift Swift
let vc = YourSharedFramework.ferretViewController()
present(vc, animated: true)

Notifications on iOS

Ferret uses UNUserNotificationCenter on iOS. To have Ferret request notification permission at startup, set requestPermission = true:

Kotlin
install(Ferret) {
    configuration = FerretConfiguration(
        notifications = NotificationConfiguration(
            requestPermission = true
        )
    )
}
Integration

Opening the Inspector

Several ways to open the Ferret inspector UI, depending on your platform and workflow.

Android Android

Notification tap

While Ferret is capturing traffic it posts a rolling system notification. Tapping it opens FerretActivity directly — no setup required beyond installing the plugin.

Home-screen shortcut

Ferret registers a dynamic launcher shortcut labeled "Open Ferret Inspector". Long-press your app's icon and select it to jump into the inspector at any time, even if the notification is dismissed.

Programmatic launch

Launch from a debug settings screen, a hidden gesture, or any other trigger:

DebugActivity.kt Kotlin
startActivity(
    Intent(context, Class.forName("com.ferret.ui.FerretActivity"))
)

iOS iOS

Notification tap

When Ferret is active and has notification permission, it posts a rolling notification via UNUserNotificationCenter. Tapping it opens the inspector directly — no extra code needed. Make sure requestPermission = true is set in NotificationConfiguration, or request the permission yourself before Ferret can show the notification.

From Kotlin / shared code

DebugHelper.kt Kotlin
FerretIosUi.open()

From Swift

DebugViewController.swift Swift
// Replace "YourSharedFramework" with your actual Kotlin/Native framework name
let controller = YourSharedFramework.ferretViewController()
present(controller, animated: true)
iOS trigger ideas

No system shortcut on iOS means you pick the trigger. Common patterns: shake the device, tap a version label five times, or add a "Debug" row in a settings screen that's only compiled in debug builds.

Customization

Ferret Configuration

All of Ferret's behavior is controlled through a single FerretConfiguration data class. Pass it to install(Ferret) { configuration = ... }. If omitted, defaults are used.

Full example

NetworkModule.kt Kotlin
install(Ferret) {
    context = applicationContext          // Android only
    configuration = FerretConfiguration(
        notifications = NotificationConfiguration(
            maxBufferSize       = 5,
            defaultPriority     = NotificationPriority.HIGH,
            defaultChannel      = NotificationChannelSpec(
                id   = "ferret_debug",
                name = "Ferret Debug"
            ),
            defaultSmallIcon    = R.drawable.ic_notification, // Android only
            requestPermission   = false
        ),
        retentionDurationHours = 24L
    )
}

FerretConfiguration properties

Property Type Default Description
notifications NotificationConfiguration NotificationConfiguration() Controls the rolling notification. See Notification Settings.
retentionDurationHours Long 12 Records older than this many hours are automatically deleted on initialization.

Retention

Ferret automatically purges records older than retentionDurationHours each time the plugin initializes. The default (12 hours) is enough for a typical development session while bounding on-device storage use. Increase it if you need to retain history across multiple app launches.

Manual clear

You can always wipe all records immediately using the trash icon in the inspector UI, regardless of the retention setting.

Customization

Notification Settings

Control how the rolling capture notification looks and behaves using NotificationConfiguration.

NotificationConfiguration properties

Property Default Description
maxBufferSize 5 Number of recent request entries shown in the notification body.
defaultPriority HIGH Notification priority — controls heads-up behavior and ranking. See NotificationPriority below.
defaultChannel NotificationChannelSpec() The Android notification channel Ferret posts to. Customise the id and display name.
defaultSmallIcon System default Drawable resource ID for the notification small icon. Android only.
requestPermission false When true, Ferret requests notification permission from the OS at initialization. iOS only — no-op on Android.

NotificationPriority

Maps to NotificationCompat priority constants on Android and UNNotificationInterruptionLevel on iOS.

Value Android behavior
MIN No heads-up; minimal ranking. Does not make a sound.
LOW No heads-up; lower-than-normal ranking.
DEFAULT Standard notification ranking. May make a sound.
HIGH May show as a heads-up notification. (default)
MAX Full urgency — use only when you really need immediate attention.

NotificationChannelSpec

Defines the Android notification channel. Channels are created once — changing properties after the channel exists requires the user to clear app data or reinstall.

Kotlin
NotificationChannelSpec(
    id   = "ferret_debug",  // unique channel ID — used internally
    name = "Ferret Debug"   // display name shown in system notification settings
)
Channel ID stability

Once your app has been installed, changing the channel id creates a new channel. The old channel — with the old settings the user may have customized — persists until you explicitly delete it or the user reinstalls. Keep the id stable across releases.

Customization

Notification Permission

Starting with Android 13 (API 33), apps must request POST_NOTIFICATIONS at runtime before they can display any notification.

Capture always works

Ferret captures and stores all network traffic regardless of notification permission status. The rolling notification simply won't appear until the permission is granted. The in-app inspector and home-screen shortcut are always available.

Ferret's responsibility

Ferret declares POST_NOTIFICATIONS in its own manifest so it merges into your app, but it does not request the permission at runtime. Your app is responsible for requesting it — the same way you would for any of your own notifications.

Requesting at runtime

If your app already requests notification permission for another feature, no extra work is needed. If not, add a runtime request somewhere sensible — first launch, an onboarding screen, or a debug settings screen.

Compose API

Kotlin · Jetpack Compose
val permissionLauncher = rememberLauncherForActivityResult(
    ActivityResultContracts.RequestPermission()
) { granted ->
    // handle the result
}

LaunchedEffect(Unit) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
    }
}

Traditional API

Kotlin
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    ActivityCompat.requestPermissions(
        this,
        arrayOf(Manifest.permission.POST_NOTIFICATIONS),
        REQUEST_CODE_NOTIFICATIONS
    )
}
iOS — set requestPermission = true

On iOS, set requestPermission = true in NotificationConfiguration. Ferret will call UNUserNotificationCenter.requestAuthorization at initialization time — no extra code from you.

Using the Inspector

Inspector Overview

The Ferret inspector is a full Compose Multiplatform UI shared between Android and iOS. Here's a tour of what it offers.

Network record list

The main screen shows every captured HTTP call and WebSocket event, newest first. Each row shows:

  • HTTP method badge (GET, POST, …) or a WebSocket indicator
  • Request URL / path
  • Response status code, color-coded by range (2xx green, 4xx orange, 5xx red)
  • Total request duration in milliseconds

Type tabs

Tab Shows
All Every captured record — HTTP and WebSocket combined
HTTP Plain HTTP request/response pairs only
WebSocket WebSocket connections and message frames only

Search

The search bar filters the active list by URL or path substring. Combine the tab filter with search to zero in on a specific request fast.

Detail screen — HTTP

Tap any HTTP record to open the detail view. Four tabs are available:

  • Overview — method, full URL, status code, timing in ms, protocol, TLS info
  • Request — all request headers and the request body
  • Response — all response headers and the response body
  • Timing — per-phase breakdown (DNS, connect, TLS, TTFB, transfer)

Detail screen — WebSocket

  • Overview — URL, connection state, total duration
  • Messages — all sent and received frames with timestamps, frame type, and byte size

Share

From the detail screen tap Share to export a request in two formats:

  • cURL command — a ready-to-run shell command you can paste into a terminal or a colleague's chat
  • Plain text — a human-readable summary suitable for filing a bug report

Copy to clipboard

Tap any individual field — URL, header name/value, body, status code — to copy it directly to the clipboard.

Clear history

The trash icon in the inspector toolbar wipes all captured records from the local database immediately. The inspector will accumulate new records as fresh requests are made.

Everything stays on-device

All captured data is stored only in the on-device Room database. Nothing is ever transmitted to an external server, logged to a remote service, or shared outside the app process.

Reference

FAQ

Common questions about Ferret.

Yes. Ferret captures WebSocket connections and all message frames — both sent and received — in addition to regular HTTP traffic. Everything appears in the same inspector list. Use the WebSocket tab to filter to WebSocket-only events.

On Android there are two built-in options requiring no code: tap the rolling notification, or long-press your app icon and select "Open Ferret Inspector". You can also launch it programmatically with startActivity(Intent(context, Class.forName("com.ferret.ui.FerretActivity"))). On iOS there is no system shortcut — present ferretViewController() from wherever makes sense in your app (a debug menu, shake gesture, etc.).

No. Ferret is implemented as a Ktor HttpClientPlugin. It is not a system proxy or VPN — it cannot intercept traffic from OkHttp, Retrofit, URLSession, or any client that doesn't go through a Ktor HttpClient. Your networking layer must use Ktor for Ferret to capture it.

Ferret is designed for development and internal builds. Gate the install(Ferret) call behind a debug flag (e.g. if (BuildConfig.DEBUG)) or swap in a plain HttpClient for release. If Ferret is not installed, it adds zero overhead — no plugin, no interception, no database.

No. Everything is stored in an on-device Room (SQLite) database. Ferret never makes outbound connections for telemetry or syncing and never sends captured data to any server. You can wipe all records at any time from the inspector UI.

Yes. After the plugin is initialized, FerretSdk.networkRecordRepository provides the full NetworkRecordRepository interface — including observeAll(): Flow<List<NetworkRecord>>, getById(id), and clear(). This is useful for writing custom test assertions or building additional debug tooling on top of Ferret.

Ferret is built and tested with Ktor 3.5.1 and requires Ktor 3.x. Ktor 2.x is not supported because the plugin API changed between major versions. Check the full Requirements table for all version details.

Reference

Developers

Ferret is built and maintained by the following developers. Contributions, issues, and feedback are welcome on GitHub.

Aditya Gupta
Aditya Gupta
Core Developer
@Aditya-gupta99
Nagarjuna
Nagarjuna
Core Developer
@Nagarjuna0033
Contributing

Found a bug or want to improve Ferret? Open an issue or pull request on GitHub. All contributions are welcome.

Reference

About & License

Ferret is an open-source Kotlin Multiplatform library licensed under the Apache License 2.0.

Why Ferret?

Inspecting mobile network traffic has traditionally required external tools: Charles Proxy, Proxyman, or Wireshark — each needing SSL certificate trust profiles, Wi-Fi proxy configuration, and a desktop machine on the same network. Ferret eliminates that friction by living inside the app process with a self-contained on-device UI. Same visibility, no external dependencies.

Technology stack

Technology Version Purpose
Kotlin Multiplatform 2.4.0 Shared business logic across Android and iOS
Compose Multiplatform 1.10.3 Shared inspector UI — one codebase, both platforms
Ktor 3.5.1 HTTP client plugin integration point
Room 2.7.1 Local SQLite persistence for captured records
kotlinx.coroutines Async operations and Flow-based data streams
kotlinx.datetime 0.7.1 Cross-platform date/time formatting
kotlinx.serialization 1.8.0 JSON body parsing

Maven coordinates

Field Value
groupId io.github.ferret-org
artifactId ferret
latest version 1.0.0
repository Maven Central

License

Ferret is licensed under the Apache License 2.0.

Copyright 2026 The Ferret Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.