Trust Insights: Detect Coerced Actions In Sensitive iOS Flows
Some security problems are not really authentication problems.
A person can pass Face ID, enter the right one-time code, and still be in danger because someone else is coaching them through the flow. The user is legitimate. The action is technically valid. The risk is that the intent may not be freely chosen.
That is the gap Apple’s new TrustInsights framework is trying to cover.
The API is beta in the iOS 27 SDKs as of this writing, so treat the exact names below as the current WWDC26 shape rather than a forever contract. The product idea is the important part: use privacy-preserving behavioral context to help detect social engineering and coercion in moments where your normal app logic cannot see the pressure around the user.
What Trust Insights is for
Trust Insights is not a fraud engine, an identity provider, or a replacement for your server-side risk model. It is a signal you can request from app code when someone is about to perform a sensitive action.
Apple’s first available insight is IsLikelyBeingCoachedInsight. It looks for indications that a person may be actively coached while performing an operation. That matters for flows where the user’s own authenticated action can still be harmful:
- sending money to a new recipient
- changing recovery email, phone number, or passkeys
- granting remote access or authorizing a new device
- exporting personal data
- sending sensitive documents
- consuming expensive infrastructure, like paid AI inference
The humane version of this is not “the app knows better than the user.” It is “the app noticed a risk pattern and can slow the moment down.”
That difference matters. A coerced user may already feel embarrassed, rushed, or afraid. Your UI should create space, not shame.
Pick the moment carefully
Trust Insights needs to be called close enough to the sensitive operation that the result is meaningful, but early enough that a couple seconds of latency does not make the app feel broken. Apple’s WWDC26 session calls out that evaluation is asynchronous, can take a few seconds, and requires Internet reachability.
That points to the review or confirmation step, not the final animation after the irreversible work is already done.
For a transfer flow, a good shape is:
- The user enters the recipient and amount.
- Your app runs its normal local and server risk checks.
- The user reaches a review screen.
- While the review screen is visible, request the trust insight.
- Use the result to choose the next step before committing the transfer.
Do not sprinkle this call across every screen in the app. Use it where behavioral context can actually change the safety decision.
Configure the operation category
The operation category is not just metadata for your logs. It tells the framework what kind of action the user is trying to perform, which affects the model logic used for the evaluation.
The beta API has five categories:
payment: payment, purchase, or exchange of money, content, or assetsaccount: registration, login, or account detail changesresourceUse: costly or constrained resource usage, such as AI inferencecommunication: sending messages, submitting forms, or signing documentsother: a fallback when the action does not fit the named categories
I would keep the mapping boring and explicit:
import TrustInsights
@available(iOS 27.0, *)
enum SensitiveOperation {
case newRecipientTransfer
case changeRecoveryEmail
case runExpensiveInferenceJob
case submitSignedDocument
case exportPersonalArchive
var trustCategory: InsightEvaluator.OperationCategory {
switch self {
case .newRecipientTransfer:
return .payment
case .changeRecoveryEmail:
return .account
case .runExpensiveInferenceJob:
return .resourceUse
case .submitSignedDocument:
return .communication
case .exportPersonalArchive:
return .other
}
}
}
other is useful, but I would not hide everything there. If your app has a high-volume sensitive flow that does not fit the current categories, Apple asks developers to file feedback. That is exactly the kind of beta feedback that can improve the shape of the framework before it hardens.
Request authorization before evaluation
Trust Insights requires the com.apple.developer.trustinsights.base entitlement. In Xcode, that means enabling the Trust Insights capability for the app target that requests evaluations.
Entitlement is only one part. The user also controls whether your app can use Trust Insights, and they can disable it in Settings. Apple’s session notes that a cooldown period may apply after disabling, to protect users who may have been coached into turning it off. Check authorization before you request an evaluation, and treat denial or unavailability as an ordinary product state. The app should fall back to its existing risk controls instead of nagging the user or failing closed for every sensitive action.
The most stable example is the same shape Apple shows in the session: build the request, build the context, then ask for authorization before evaluating.
import TrustInsights
@available(iOS 27.0, *)
func requestTrustInsightsAuthorization(
operation: SensitiveOperation,
evaluator: InsightEvaluator
) async -> Bool {
let request = IsLikelyBeingCoachedInsight.request(
schema: .version1,
modelVersion: .current
)
let context = InsightEvaluator.InsightContext(
operationCategory: operation.trustCategory,
requestedEvaluations: request
)
do {
return try await evaluator.requestAuthorization(for: context) == .authorized
} catch {
return false
}
}
In production, check the current authorizationStatus(for:) API before prompting so you can present your own short explanation only when a request makes sense. Something like “This helps us detect patterns often seen in scams before high-risk actions. It does not share your messages, photos, or mail content.” Then let the system prompt do its job.
Request the coaching insight
The core request in Apple’s transcript is small: create an IsLikelyBeingCoachedInsight request, put it in an InsightEvaluator.InsightContext, check authorization, and call requestEvaluation.
Here is a version wrapped in a small service:
import TrustInsights
@available(iOS 27.0, *)
struct CoachingRiskEvaluator {
enum Result {
case unavailable
case noAdditionalSignal
case addLightFriction
case addStrongFriction
}
private let evaluator = InsightEvaluator()
func evaluate(operation: SensitiveOperation) async -> Result {
let request = IsLikelyBeingCoachedInsight.request(
schema: .version1,
modelVersion: .current
)
let context = InsightEvaluator.InsightContext(
operationCategory: operation.trustCategory,
requestedEvaluations: request
)
do {
guard try await evaluator.requestAuthorization(for: context) == .authorized else {
return .unavailable
}
let assessment = try await evaluator.requestEvaluation(context: context)
return consume(assessment)
} catch {
return .unavailable
}
}
private func consume(_ assessment: InsightEvaluation<IsLikelyBeingCoachedInsight>) -> Result {
do {
switch try assessment.insight.outcome.get() {
case .unknown:
assessment.reportConsumption(.usedUnchangedFriction)
return .noAdditionalSignal
case .medium:
assessment.reportConsumption(.usedIncreasedFriction)
return .addLightFriction
case .high:
assessment.reportConsumption(.usedIncreasedFriction)
return .addStrongFriction
@unknown default:
assessment.reportConsumption(.notUsedError)
return .unavailable
}
} catch {
assessment.reportConsumption(.notUsedError)
return .unavailable
}
}
}
Two notes about this example.
First, requestAuthorization(for:) is convenient for showing the core flow, but a real app should usually call authorizationStatus(for:) first so it can decide whether to present its own explanation screen.
Second, the modelVersion argument is optional. Apple’s session mentions that requesting current and prior versions can help with model governance and validation. That is useful for teams that want to sample a newer model before wiring it into decision logic, but most first integrations should keep that code path simple until they have a reason to compare versions.
Unknown is not low risk
IsLikelyBeingCoachedInsight currently returns three outcome values:
unknown: no evidence of scam risk was foundmedium: some evidence of coaching riskhigh: significant evidence of coaching risk
The trap is reading unknown as safe.
It is not. It means the system does not have evidence of coaching in that evaluation. It does not mean the recipient is trustworthy, the transfer is legitimate, the document is harmless, or the account change is fine. Your normal risk checks still matter.
That is why I like naming the app-level result noAdditionalSignal instead of lowRisk:
enum SensitiveFlowRisk {
case blockedByExistingControls
case noAdditionalTrustSignal
case coachingSignalMedium
case coachingSignalHigh
}
This little naming choice prevents a future bug where someone writes:
if trustInsight == .unknown {
approveTransfer()
}
The better mental model is:
unknown: continue with the risk posture you already hadmedium: add friction, education, or verificationhigh: inform the user clearly and add stronger friction before proceeding
Trust Insights should work alongside your existing fraud, abuse, and account-protection systems. It should not be the sole factor in a decision.
Report consumption for every returned evaluation
Real-time feedback is part of the integration, not a nice-to-have. Apple’s session says to call reportConsumption for each returned evaluation result, even when the result did not change the current flow, and warns that omitting feedback may lead to rate limiting.
The consumption value should describe how the insight affected the app:
.usedReducedFriction: the insight made the operation easier.usedUnchangedFriction: the insight was evaluated but did not change the flow.usedIncreasedFriction: the insight caused added checks or friction.notUsedNotNeeded: the operation was canceled, so no decision was needed.notUsedError: a technical problem or timing issue prevented use.usedEvaluationOnly: the insight was used for internal evaluation without changing the user experience
I would make that hard to forget by keeping evaluation and consumption close together:
@available(iOS 27.0, *)
func intervention(
for assessment: InsightEvaluation<IsLikelyBeingCoachedInsight>,
normalRiskScore: Int
) -> SensitiveFlowIntervention {
do {
switch try assessment.insight.outcome.get() {
case .unknown:
assessment.reportConsumption(.usedUnchangedFriction)
return normalRiskScore > 80 ? .manualReview : .continueNormally
case .medium:
assessment.reportConsumption(.usedIncreasedFriction)
return .showScamEducationAndDelay
case .high:
assessment.reportConsumption(.usedIncreasedFriction)
return .requireOutOfBandVerification
@unknown default:
assessment.reportConsumption(.notUsedError)
return .continueWithExistingControls
}
} catch {
assessment.reportConsumption(.notUsedError)
return .continueWithExistingControls
}
}
If the user cancels after the evaluation but before the app makes a decision, report .notUsedNotNeeded. If the result arrives too late to affect the current operation, report .notUsedError. The point is to tell the system what actually happened, not what you hoped would happen.
Keep offline fraud labels minimal
There is a second feedback loop for cases where a transaction later turns out to be fraudulent. That feedback does not come from the app in real time. Apple’s session describes submitting offline labels through Apple Business Register using a server-to-server API and a defined schema that includes the insight identifier from the original evaluation.
This is where data minimization has to stay boring and strict.
If your app might participate in offline feedback, store only what you need to connect a confirmed fraud report back to the original evaluation:
struct TrustInsightAuditRecord: Codable {
let operationID: String
let insightID: String
let modelVersion: String?
let newestModelVersion: String?
let evaluatedAt: Date
}
@available(iOS 27.0, *)
func makeAuditRecord(
operationID: String,
assessment: InsightEvaluation<IsLikelyBeingCoachedInsight>
) -> TrustInsightAuditRecord {
TrustInsightAuditRecord(
operationID: operationID,
insightID: assessment.insight.insightID,
modelVersion: assessment.insight.modelVersion,
newestModelVersion: assessment.insight.newestModelVersion,
evaluatedAt: Date()
)
}
operationID should be your own opaque internal identifier, not a recipient’s name, email, phone number, or memo text. Keep the sensitive case details in your own fraud system where they already belong. The Apple Business Register submission should not become a side channel full of extra personal information.
Offline labels are not required to benefit from Trust Insights, but they help improve the ecosystem when your business has confirmed fraud outcomes to report.
Privacy is part of the product design
Trust Insights is interesting because Apple is trying to make the signal useful without asking every app to collect a pile of behavioral data.
The WWDC26 session describes a privacy-preserving design: device-sourced signals are processed locally, inputs are discarded after evaluation, and device-derived data is not shared with Apple or third parties. The framework looks at patterns such as interaction timing, context, and basic sensor data, not the content of Photos, Messages, or Mail. A single derived device value can leave the device for evaluation, and the final result may also incorporate Apple Account signals and velocity checks.
That does not absolve your app from privacy work.
Your responsibilities are still clear:
- ask only in sensitive flows where the signal can matter
- explain the benefit before requesting authorization
- avoid logging raw Trust Insights context around the user
- store only identifiers needed for audit and offline feedback
- do not submit surplus personal information in offline labels
- keep Trust Insights decisions separate from unrelated analytics
Security features can quietly become surveillance-shaped if nobody trims the data. Keep this one narrow.
Design friction that helps
The best response to a medium or high coaching signal is usually friction plus education. Not a scary modal. Not a permanent account lock. Not a vague “suspicious activity detected” message that leaves the user confused.
For a medium result, you might:
- add a short delay before final confirmation
- show a plain-language scam warning
- ask the user to verify the recipient through another channel
- require re-entering the recipient or amount
- route the operation through your existing risk score
For a high result, you might:
- require out-of-band verification
- hold the transaction for manual review
- force a cooling-off period for irreversible changes
- provide a direct support path
- explain the specific risky pattern without exposing model internals
The copy should be calm and non-accusatory:
Take a moment before continuing.
Scammers sometimes stay on a call or chat and guide people through transfers.
If someone is telling you to keep this secret, pause and contact support or
the recipient through a trusted channel.
That kind of message is useful even when the user is not being scammed. It teaches a pattern. It gives the person permission to slow down. It also avoids telling an attacker exactly which signal fired.
Outright blocking based only on a trust insight is a bad default. There are cases where your combined risk model may still deny or hold an operation, but Trust Insights should be one input into a broader decision. The safer product stance is “we add care at the dangerous moment” rather than “the model says no.”
Test the decision tree
During development, Trust Insights requests use a sandbox environment. Apple’s session also mentions using Xcode scheme configuration to override insight values and errors so you can test decision logic and UX variants.
Use that. Your QA matrix should include:
- authorization not determined
- authorization denied
- framework unavailable
- no network or evaluation timeout
unknownmediumhigh- insight-level error
- result arrives after the user canceled
The unknown path deserves just as much test coverage as high. That is where apps are most likely to accidentally skip existing checks because the new API “didn’t find anything.”
Where this fits
Trust Insights is one of those APIs where the integration code is not the hard part. The hard part is deciding when your app has earned the right to ask, what you do with uncertainty, and how you talk to a person who may be under pressure.
My starting checklist would be:
- identify high-impact, reversible and irreversible flows
- map each flow to the closest operation category
- add the entitlement and authorization flow
- place evaluation on a review or interstitial screen
- handle network and authorization failure as normal states
- never treat
unknownas low risk - report consumption for every returned evaluation result
- store only minimal audit identifiers for possible offline labels
- design warnings that help people slow down without shaming them
The promise of Trust Insights is not that your app can perfectly know intent. It cannot. The promise is that, at the moments that matter, your app can notice a useful signal and respond with care.
Further reading: