Developer How-To · Kotlin

Add e-signatures
to a Kotlin app

Four steps using OkHttp, Gson, and coroutines — no SDK, no boilerplate. Works on JVM, Spring Boot, Ktor, and Android.

1

Add dependencies

Add OkHttp for HTTP, Gson for JSON, and Kotlin coroutines. These three libraries cover everything the GetSigned integration needs — no dedicated SDK required.

kotlin
// build.gradle.kts
dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0")
    implementation("com.google.code.gson:gson:2.10.1")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
}
2

Authenticate with OAuth2

Exchange your client credentials for a bearer token. The token is valid for 3600 seconds — cache it in memory and refresh when it expires.

kotlin
import okhttp3.*
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

private val gson = Gson()
private val http = OkHttpClient()

data class TokenResponse(val access_token: String, val expires_in: Int)

suspend fun getToken(clientId: String, clientSecret: String): String =
    withContext(Dispatchers.IO) {
        val body = FormBody.Builder()
            .add("grant_type",    "client_credentials")
            .add("client_id",     clientId)
            .add("client_secret", clientSecret)
            .build()
        val req = Request.Builder()
            .url("https://api.getsigned.app/oauth/token")
            .post(body)
            .build()
        http.newCall(req).execute().use { res ->
            gson.fromJson(res.body!!.string(), TokenResponse::class.java).access_token
        }
    }
3

Create an envelope

Upload the PDF as a multipart form request alongside signers and field coordinates. The API returns an envelope ID you use to send the document.

kotlin
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody

data class EnvelopeResponse(val id: String, val status: String)

suspend fun createEnvelope(
    token:    String,
    pdfBytes: ByteArray,
    signerName:  String,
    signerEmail: String,
): String = withContext(Dispatchers.IO) {
    val multipart = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart(
            "document", "contract.pdf",
            pdfBytes.toRequestBody("application/pdf".toMediaType())
        )
        .addFormDataPart("signers",
            """[{"name":"$signerName","email":"$signerEmail"}]""")
        .addFormDataPart("fields",
            """[{"type":"signature","page":1,"x":300,"y":580,"w":200,"h":60}]""")
        .build()

    val req = Request.Builder()
        .url("https://api.getsigned.app/v1/envelopes")
        .header("Authorization", "Bearer $token")
        .post(multipart)
        .build()

    http.newCall(req).execute().use { res ->
        gson.fromJson(res.body!!.string(), EnvelopeResponse::class.java).id
    }
}
4

Send and verify webhooks

Calling /send dispatches the signing link. When the signer completes, GetSigned POSTs to your webhook. Verify the HMAC-SHA256 signature before processing.

kotlin
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest

suspend fun sendEnvelope(token: String, envelopeId: String) =
    withContext(Dispatchers.IO) {
        val req = Request.Builder()
            .url("https://api.getsigned.app/v1/envelopes/$envelopeId/send")
            .header("Authorization", "Bearer $token")
            .post("".toRequestBody())
            .build()
        http.newCall(req).execute().close()
    }

// In your webhook handler (Ktor / Spring Boot):
fun verifyHmac(rawBody: ByteArray, sigHeader: String, secret: String): Boolean {
    val mac = Mac.getInstance("HmacSHA256")
    mac.init(SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256"))
    val computed = mac.doFinal(rawBody)
        .joinToString("") { "%02x".format(it) }
    // MessageDigest.isEqual is constant-time — don't use String.equals() here
    return MessageDigest.isEqual(
        computed.toByteArray(Charsets.UTF_8),
        sigHeader.toByteArray(Charsets.UTF_8),
    )
}

// envelope.completed → download sealed PDF:
// GET /v1/envelopes/{id}/document
//   Authorization: Bearer $TOKEN

Frequently asked questions

Which Kotlin frameworks work with GetSigned?

Any Kotlin application that can make HTTP requests works with GetSigned — the API is plain REST. For server-side Kotlin, OkHttp works with Spring Boot, Ktor, Micronaut, or any JVM framework. For Android, OkHttp is standard. For multiplatform (KMP), you can use Ktor Client instead of OkHttp for a single codebase targeting JVM, Android, and iOS.

How do I handle the GetSigned API in a coroutine-based Kotlin app?

Wrap all OkHttp calls in withContext(Dispatchers.IO) to move blocking network I/O off the main dispatcher. OkHttp doesn't have a native coroutine API, but the withContext wrapper is idiomatic and sufficient. If you prefer a fully suspending HTTP client, Ktor Client (ktor-client-okhttp on JVM) provides suspend-native API calls and is a good alternative for new projects.

Can I use GetSigned in an Android app?

Yes. OkHttp is the standard HTTP client for Android. Add the dependency to your module's build.gradle.kts, wrap calls in Dispatchers.IO, and the integration works as shown. For Android specifically, store your client credentials in a server-side backend — never embed them in the APK. Your Android app should call your backend, which calls GetSigned, so credentials are never client-side.

How do I verify webhook signatures in Ktor?

Receive the raw request body as a ByteArray before any JSON deserialization — parsing first can alter whitespace and invalidate the signature. Extract the X-GetSigned-Signature header, compute HMAC-SHA256 of the raw bytes with your webhook secret using javax.crypto.Mac, hex-encode the result, and compare to the header value. Use a constant-time comparison (not equals()) to prevent timing attacks.

Is there a Kotlin SDK, or do I use the REST API directly?

There is no dedicated Kotlin SDK — you use the REST API directly with standard HTTP libraries. This is intentional: it means there is no SDK to update, no dependency conflicts, and no abstraction layer between you and the API. OkHttp + Gson covers the full integration in under 100 lines of idiomatic Kotlin.

Related: Java guide · Node.js guide · Python guide · Webhook guide

Send your first envelope today

Free tier — 25 envelopes per month. Full API access from day one.

Get free API keys →