SDK Integration

The integration contract before you request access

Two things decide whether it works: what your payload has to match, and which of the two paths an action takes. Both are on this page.

In Short

How do you integrate the Yuthent SDK?

You install an interceptor in your app and a verify step in front of the route you are protecting. Your existing HTTP call does not change: when your backend asks for an approval, the interceptor runs the ceremony on the device and retries the same request with the result attached. The signature is made in the phone's secure hardware and released by the person's fingerprint or face; the biometric never leaves the device. Your backend verifies before your handler runs, and stays the enforcement point.

In Code

Two places your code changes. That is the whole surface.

One install in the app, one step in front of the route. The shapes below are the SDKs' own; the packages, the endpoints and the reference documentation are delivered with access.

in the app
// Android โ€” install once, where you build your HTTP client.
val client = OkHttpClient.Builder()
    .addInterceptor(
        YuthentOkHttpInterceptor.Builder()
            .activityProvider { currentActivity }
            .onboardingTrigger { url -> startEnrolment(url); true }
            .build()
    )
    // Must outlast the approval, or OkHttp cancels the ceremony.
    .callTimeout(90, TimeUnit.SECONDS)
    .build()

// iOS โ€” the same idea, registered globally.
YuthentURLProtocol.register { url in startEnrolment(url) }

// Your existing call does not change. When your backend asks for an
// approval, the interceptor runs the ceremony on the device and
// retries this same request with the result attached.
val response = client.newCall(request).execute()
in your backend
// Your backend โ€” the verify step in front of the route.
// The level comes from your own policy, per action.
const approved = yuthent(policy.levelFor("transfer"), {
  apiKey: process.env.YUTHENT_API_KEY,
});

app.post("/transfer", approved, (req, res) => {
  // Reached only on an approval that verified. Unchanged otherwise.
  runTransfer(req.body);
});

// The other path: an agent acting under a mandate the person
// already signed. Ask before you act, and handle three answers.
const { decision, authorizationJws } = await authorizeUnderMandate({
  actionType: "transfer",
  actionContextId: ref,
  riskLevel: "HIGH",
});

switch (decision) {
  case "MANDATE_AUTHORIZED":
    // Inside the bounds the person sealed. Verify, then act.
    await verifyMandateAuthorization(authorizationJws);
    return runTransfer(req.body);
  case "STEP_UP_REQUIRED":
    // A bound was exceeded. This becomes a human approval.
    return askThePerson();
  default:
    // No mandate covers it, or the one that did was revoked.
    return refuse();
}
01

Your app asks

Your call goes out as it always did. The interceptor sees your backend ask for an approval and takes over from there. You pass an actionType, an actionContextId and a riskLevel. No PII.

02

The device answers

The SDK runs the approval on the device and signs it there, with a key held in Android's StrongBox or the TEE, or in the iOS Secure Enclave, released by the person's fingerprint or face. The biometric never leaves the phone.

03

Your backend verifies

The signed approval comes back on the retry. Your backend checks it against the action it was made for, and gets a verified decision rather than a value to trust.

04

You execute, or you do not

Your handler runs only after verification succeeds. Your server stays the enforcement point, which is the whole reason the check sits in it rather than in ours.

Action Binding

What you send must match what they approved

You send the action payload with the approval. If what you send does not match what the device signed, verification fails and you do not execute. That is the whole integration rule, and it is the one worth writing a test for before you ship.

Your server re-derives what the person was shown and compares it to what the signature covers. If the two differ by a character, the approval does not verify and your handler does not run.

Two ways to trip it

Either the payload changed between signing and verifying, or it never arrived. From the verifierโ€™s side those are the same failure: it cannot confirm what the person saw. Both come back as a named rejection code, which is defined in the reference documentation that ships with access.

You cannot turn it off

At the levels that carry a payload the check is unconditional and fails closed. There is no tenant setting, no flag and no migration path that disables it, which is worth knowing before you design around it.

The same result on every platform

Android, iOS and the server produce identical results from the same payload. You do not need a per-platform branch, and a mismatch is a bug in your payload rather than a difference between the SDKs.

The Two Paths

Not every authorized action produces a prompt

If you build assuming a person is asked every time, you will build the wrong contract and it will fail the first time a customer issues a standing mandate. There are two ways an action becomes authorized and your client has to handle both.

Per action

The person is asked at the moment of the action. You get a signed approval back and verify it before you execute. This is the path the interceptor drives, and the one the code above shows first.

Under a standing mandate

The person signed the bounds once: which action types, up to what ceiling, until when. Inside those bounds the action is authorized without interrupting anyone. Nobody is prompted, and that is the design rather than a gap in it.

On the mandate path you ask before you act, and you handle three answers.

MANDATE_AUTHORIZED

Inside the bounds the person sealed. You verify the returned authorization and proceed on something you can keep.

STEP_UP_REQUIRED

A bound was exceeded, or an oversight actor asked to see it. The action pauses and becomes an approval on the accountable person's own device.

MANDATE_REJECTED

No mandate covers the action, or the one that did was revoked or has expired. It fails and does not retry.

Yuthent decides; your gate enforces. Until your own endpoint requires the check, a second path to the same operation reaches it without one.

Risk Level

The level you pass decides whether it can work offline.

Your own policy sets a riskLevel per action. The value you pass is the one thing that changes how the SDK behaves when the phone has no network, so it is worth getting right before you ship.

LOW, MEDIUM, HIGH

Can be approved offline

The approval is produced on the device and queued. Your backend syncs and verifies it when the phone reconnects, inside a window your policy sets. Connectivity is not needed at the moment of the action.

Clock-in, field approvals, card-not-present, moderate-value transfers

CRITICAL

Blocks on the network

The most consequential actions wait for your backend to countersign before they run. Offline, a CRITICAL action is refused rather than queued, so it never executes on an approval nobody has verified.

High-value transfers, a new beneficiary, account recovery, privileged change

Data & Privacy

We do not need your business data.

Worth reading before your privacy review asks, because the answer is shorter than they expect.

Biometrics stay on the device

Nothing in either SDK captures or transmits a biometric. Your backend sees decisions and references, and there is no biometric for anyone to receive.

Context ids, not PII

The payload is an actionType, an actionContextId and a riskLevel. No names, no emails, no amounts.

Your data stays yours

The SDK sends what verification needs and nothing else. Your business logic and your customer records never leave your system.

With Access

What you get when you ask.

The page above is the contract. This is the material, and it is issued with a deployment rather than published to a registry.

The SDKs

Native packages for Android in Kotlin and iOS in Swift, and the server-side middleware. Issued with a deployment rather than published to a public registry.

The reference documentation

Endpoints, the full response and rejection codes including the one action binding returns, the retry contract, and the error cases your client has to handle.

A tenant to build against

An isolated environment with its own keys, plus the control plane to watch what your integration is producing while you build it.

Keep reading

The architecture these properties come from is on the technology page, and what we do and do not claim about it is on the security page. Already a customer? The control plane has your keys, environments and monitoring.

See it on your own flow.

Your app, your call, our SDK.

Access details within one business day, from a person.