Developer How-To · Swift

Add e-signatures
to a Swift app

URLSession and CryptoKit only — no third-party libraries. Works on iOS, macOS, and server-side Swift. Includes a security note on credential placement.

1

No dependencies required

URLSession handles HTTP and multipart uploads. CryptoKit provides HMAC-SHA256 for webhook verification. Both are built into Apple platforms — add nothing to Package.swift.

swift
// Swift 5.9+ — iOS 16+, macOS 13+
// URLSession  → HTTP / multipart uploads
// CryptoKit   → HMAC-SHA256 webhook verification
// Both are in the Apple SDK — no Package.swift changes needed.

// ⚠️  Security note: store client credentials in your server-side
// backend — NEVER embed them in an iOS/macOS app binary or bundle.
// Your app should call your backend, which calls GetSigned.
2

Authenticate with OAuth2

Exchange your client credentials for a bearer token. Cache the token and refresh 60 seconds before the 3600-second expiry.

swift
import Foundation

struct TokenResponse: Decodable {
    let access_token: String
    let expires_in: Int
}

func getToken(clientId: String, clientSecret: String) async throws -> String {
    var request = URLRequest(url: URL(string: "https://api.getsigned.app/oauth/token")!)
    request.httpMethod = "POST"
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    request.httpBody = [
        "grant_type=client_credentials",
        "client_id=\(clientId)",
        "client_secret=\(clientSecret)"
    ].joined(separator: "&").data(using: .utf8)

    let (data, _) = try await URLSession.shared.data(for: request)
    return try JSONDecoder().decode(TokenResponse.self, from: data).access_token
}
3

Create an envelope

Upload the PDF as a multipart form request with signer details and field coordinates. The response includes the envelope ID you use to send.

swift
struct EnvelopeResponse: Decodable { let id: String; let status: String }

func createEnvelope(
    token: String,
    pdfData: Data,
    signerName: String,
    signerEmail: String
) async throws -> String {
    let boundary = UUID().uuidString
    var body = Data()

    func append(_ string: String) { body.append(string.data(using: .utf8)!) }
    func field(_ name: String, _ value: String) {
        append("--\(boundary)\r\nContent-Disposition: form-data; name=\"\(name)\"\r\n\r\n\(value)\r\n")
    }

    field("signers", "[{\"name\":\"\(signerName)\",\"email\":\"\(signerEmail)\"}]")
    field("fields",  "[{\"type\":\"signature\",\"page\":1,\"x\":300,\"y\":580,\"w\":200,\"h\":60}]")

    append("--\(boundary)\r\nContent-Disposition: form-data; name=\"document\"; filename=\"doc.pdf\"\r\n")
    append("Content-Type: application/pdf\r\n\r\n")
    body.append(pdfData)
    append("\r\n--\(boundary)--\r\n")

    var req = URLRequest(url: URL(string: "https://api.getsigned.app/v1/envelopes")!)
    req.httpMethod = "POST"
    req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    req.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    req.httpBody = body

    let (data, _) = try await URLSession.shared.data(for: req)
    return try JSONDecoder().decode(EnvelopeResponse.self, from: data).id
}
4

Send and verify webhooks

Calling /send dispatches the signing link. When signing completes, GetSigned POSTs to your server webhook. Use CryptoKit to verify the HMAC-SHA256 signature.

swift
import CryptoKit

func sendEnvelope(token: String, envelopeId: String) async throws {
    var req = URLRequest(url: URL(string: "https://api.getsigned.app/v1/envelopes/\(envelopeId)/send")!)
    req.httpMethod = "POST"
    req.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    _ = try await URLSession.shared.data(for: req)
}

// In your server-side webhook handler (Vapor / Hummingbird):
func verifyHmac(body: Data, sigHeader: String, secret: String) -> Bool {
    guard let keyData = secret.data(using: .utf8) else { return false }
    let key  = SymmetricKey(data: keyData)
    let mac  = HMAC<SHA256>.authenticationCode(for: body, using: key)
    let hex  = Data(mac).map { String(format: "%02x", $0) }.joined()
    // Constant-time compare — equal length, accumulate XOR, no early return
    let a = Array(hex.utf8), b = Array(sigHeader.utf8)
    guard a.count == b.count else { return false }
    var diff: UInt8 = 0
    for i in 0..<a.count { diff |= a[i] ^ b[i] }
    return diff == 0
}

// envelope.completed → download the sealed PDF:
// GET /v1/envelopes/{id}/document
// Authorization: Bearer <token>

Frequently asked questions

Should I call GetSigned directly from an iOS app?

No. Never embed OAuth2 client credentials in an iOS or macOS app binary — they can be extracted from the IPA. The correct architecture is: your iOS app calls your own backend API, and your backend calls GetSigned with the credentials stored server-side. The Swift code in this guide is intended for server-side Swift (Vapor, Hummingbird) or for your backend team. If you need to trigger signing from an iOS app, expose your own API endpoint that your app calls.

Which Apple platforms is this compatible with?

URLSession async/await requires iOS 15+ / macOS 12+. CryptoKit requires iOS 13+ / macOS 10.15+. The multipart body construction works on all platforms with Data and String support. For iOS 14 or earlier, replace async/await with completion handlers using URLSession.dataTask — the HTTP logic is identical.

Can I use Vapor or Hummingbird for the webhook endpoint?

Yes — the webhook handler works with any Swift HTTP server. The verifyHmac function takes raw body Data and the signature header string, and returns a Bool. In Vapor: read req.body.collect(max:), extract the X-GetSigned-Signature header, and call verifyHmac before parsing the JSON payload. Always read the raw body before deserialization — JSON decoding can change whitespace and break the signature.

Is there a Swift SDK for GetSigned?

No dedicated SDK — the integration uses URLSession and CryptoKit directly. This is intentional: it means there are no third-party dependencies to conflict with your project, no SDK version to track, and no abstraction between you and the API. The full integration is under 120 lines of idiomatic Swift.

How do I handle the PDF download in Swift?

Call GET /v1/envelopes/{id}/document with the bearer token. The response body is the sealed PDF as binary data. In URLSession: use URLSession.shared.data(for: request) and write the resulting Data to a file URL with Data.write(to:). In Vapor: stream the response body directly to the client using req.client.get() and Response.body.

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

Send your first envelope today

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

Get free API keys →