> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chmodlab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling the result

> Every outcome the iOS SDK returns, error codes, navigation, and troubleshooting.

`ChmodKycResult` is a Swift enum, so the compiler makes sure you cover every case.

```swift lines theme={null}
switch result {

case let .completed(transactionID):
    // The user finished uploading everything
    break

case let .cancelled(transactionID):
    // The user backed out on purpose
    break

case let .failed(transactionID, code, message):
    // Something went wrong; `code` tells you what
    break
}
```

The callback always runs on the main actor, so you can update your UI directly.

Every case carries the transaction ID, and `result.transactionID` reads it without a
`switch` when that is all you need.

## `.completed` does not mean approved

It means the user finished the flow successfully. The actual verdict is produced afterwards and delivered to **your backend through a [webhook](/results/webhooks)**, usually within seconds.

<Warning>
  Show something like *"We're reviewing your document"* at `.completed`, and update the
  user once your backend receives the webhook. A success screen here will be wrong for
  every user who is later rejected.
</Warning>

## Navigating with the result

`ChmodKycResult` and `ChmodKycFailureCode` both conform to `Hashable`, so you can push the
outcome straight onto a `NavigationPath`:

```swift lines theme={null}
struct VerificationScreen: View {

    @State private var path = NavigationPath()
    @State private var request: ChmodKycRequest?

    var body: some View {
        NavigationStack(path: $path) {
            Button("Verify my identity") {
                Task { await startVerification() }
            }
            .chmodKycVerification(request: $request) { result in
                path.append(result)
            }
            .navigationDestination(for: ChmodKycResult.self) { result in
                VerificationOutcomeScreen(result: result)
            }
        }
    }
}
```

Or map it onto your own route type when you want to control the destinations:

```swift lines theme={null}
.chmodKycVerification(request: $request) { result in
    switch result {
    case let .completed(id):       path.append(Route.underReview(id))
    case .cancelled:               break
    case let .failed(_, code, _):  path.append(Route.failure(code))
    }
}
```

## Error codes

| Code | What happened | Suggested handling |
| - | - | - |
| `.cameraPermissionDenied` | Camera access was refused | Point the user to Settings |
| `.locationPermissionDenied` | Location access was refused | Point the user to Settings |
| `.documentScanMaxRetries` | Too many failed scan attempts | Offer human support |
| `.livenessError` | The face check failed | Offer to retry |
| `.unknownError` | Unexpected failure | Show `message`, allow retry |
| `.unrecognized(String)` | A code from a newer SDK than this package | Log it, show `message` |

<Info>
  `.unrecognized` exists so a new code introduced by the SDK never breaks your build or
  crashes your app. Handle it as a generic failure and log the raw string so you can see
  what shipped.
</Info>

```swift lines theme={null}
func handle(_ result: ChmodKycResult) {
    switch result {
    case .completed:
        show("We're reviewing your document. We'll let you know shortly.")

    case .cancelled:
        dismiss()

    case let .failed(_, code, message):
        switch code {
        case .cameraPermissionDenied, .locationPermissionDenied:
            promptToOpenSettings()

        case .documentScanMaxRetries:
            offerSupport()

        case .livenessError:
            offerRetry()

        case .unknownError:
            show(message ?? "Something went wrong. Please try again.")

        case let .unrecognized(raw):
            logger.warning("Unknown chmod failure code: \(raw)")
            show(message ?? "Something went wrong. Please try again.")
        }
    }
}
```

## Troubleshooting

| Symptom | Cause |
| - | - |
| App terminates when the camera opens | `NSCameraUsageDescription` missing from `Info.plist` |
| App dies the moment the flow is presented | `CADisableMinimumFrameDurationOnPhone` missing from `Info.plist` |
| The flow crashes on launch | One of `primary`, `onPrimary`, `background` or `surface` left `nil` |
| The button does nothing | The token request threw and the error was not caught |
| An overridden string does not change | Key typo — the SDK ignores unknown keys silently |
| Result never arrives | The callback is only invoked once per flow; make sure it is not being replaced mid-flight |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.