Docs/Mobile SDK/Use Cases/Approve a wire only at HQ

Approve a wire only at HQ

Your treasury team approves outgoing wires in your iOS or Android app. Your policy says an approval counts only when the phone that approves it is at your headquarters. This page shows the call your app makes when the user taps Approve, and the check your backend runs before it counts the approval.

What you need

1. Define HQ as a region

Use a disc: a center point and a radius in meters. The coordinates below are the example from the OctetRegion reference. Replace them with your own.

let hq = OctetRegion.disc(
    center: LatLon(37.422, -122.084),
    radiusMeters: 250
)
val hq = OctetRegion.disc(LatLon(37.422, -122.084), 250.0)

2. Ask for a proof when the user taps Approve

Tie the proof to this one approval, so it can't be reused for another wire. Your backend creates a one-time nonce for the wire, and your app passes it as sessionNonce. The SDK commits a hash of the nonce inside the signed proof. Your backend accepts each nonce once. See Proof Binding.

To confirm before publishing

Proof Binding describes sessionNonce for one login and decisionRef for one authorization decision. A wire approval is an authorization decision, but the relying party fetches a decisionRef proof from Octet, and the verifier docs don't show that fetch yet. This draft uses sessionNonce so that the whole check stays on your backend. Confirm which one this page should use.

let nonce = try await backend.approvalNonce(for: wire.id)
let v = await sdk.loc.isWithin(region: hq, sessionNonce: nonce)

switch v.result {
case .yes:
    let bytes = v.proof!.proofBytes
    try await backend.submitApproval(for: wire.id, proofBytes: bytes)
case .no:
    show("Wire approvals count only at HQ.")
case .indeterminate:
    show("We couldn't confirm your location. Try again.")
}
val nonce: ByteArray = backend.approvalNonce(wire.id)
val v = sdk.loc.isWithin(hq, sessionNonce = nonce)

when (v.result) {
    OctetVerdict.Result.YES ->
        backend.submitApproval(wire.id, v.proof!!.proofBytes)
    OctetVerdict.Result.NO ->
        show("Wire approvals count only at HQ.")
    OctetVerdict.Result.INDETERMINATE ->
        show("We couldn't confirm your location. Try again.")
}

backend is your own API client and show is your own UI code. proofBytes is the serialized proof. See Serialization.

The app submits only on YES. The message for INDETERMINATE is the same whatever the reason, so it doesn't tell someone running a fake-location app which check stopped them.

3. Check the proof on your backend

Count the approval only when all five of these hold:

  1. The proof is authentic.
  2. The proof is at the VERIFIED assurance tier.
  3. The proof is fresh.
  4. The proof claims your HQ disc and an inside answer.
  5. The proof commits the hash of the nonce you issued for this wire, and that nonce hasn't been used before.

One command covers the first three. --require-verdict verified fails a disc answer signed below the VERIFIED tier, and the verifier rejects a proof older than its freshness limit.

# proof.bin holds the proofBytes the app sent with the approval
if octet-verify proof.bin --require-verdict verified; then
  echo "authentic, verified tier, fresh"   # go on to checks 4 and 5
fi

octet-verify exits 0 only when the proof is authentic. See Verifier Quick Start for the other exit codes.

To confirm before publishing

Checks 4 and 5 need exact commands. --expect-region <id> asserts a claimed region, but the id format for a disc isn't documented. Verifying Proofs says your backend compares the committed nonce hash with the nonce it issued, but not how it reads the hash from the verifier's output (--json is the likely route). Also confirm the default freshness limit: the sample output shows 300 seconds.

What each case returns

On the phone result reason Proof What happens
At HQ YES OK Attached The app submits. Your backend checks the proof and counts the approval
Outside HQ NO OK Attached The app shows "Wire approvals count only at HQ." Nothing is submitted
A fake-location app is running INDETERMINATE MOCK_LOCATION_DETECTED None The app asks the user to try again. Nothing is submitted
No usable location fix INDETERMINATE NO_FIX None The app asks the user to try again. Nothing is submitted
Simulator or emulator INDETERMINATE NO_FIX None Test on a real device
To confirm before publishing

Most wire approvals happen at a desk, indoors. The FAQ's advice for NO_FIX indoors is to step outside or move near a window. Confirm how often a disc check at HQ returns NO_FIX indoors, and what this page should recommend.

Octet answers where the phone is. Your backend decides whether the approval counts.

  • Regions: other shapes, such as a country or a US state.
  • Verdicts: every reason code and what to do with it.
  • Proof Binding: sessionNonce, decisionRef and forceFresh.
TermsPrivacy© 2026 Understone, Inc. d/b/a Octet