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
- The SDK running in your app. See the iOS Quick Start or Android Quick Start.
octet-verify1.6.0 or later on your backend. See Verifier Quick Start.- The latitude and longitude of your HQ.
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.
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:
- The proof is authentic.
- The proof is at the
VERIFIEDassurance tier. - The proof is fresh.
- The proof claims your HQ disc and an inside answer.
- 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.
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 |
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.
Related
- Regions: other shapes, such as a country or a US state.
- Verdicts: every reason code and what to do with it.
- Proof Binding:
sessionNonce,decisionRefandforceFresh.