Privacy Policy
Beckon Privacy Policy
Status: DRAFT — needs counsel review before launch. This document is grounded in our internal anonymity threat model and accurately describes the code’s actual behavior as of the latest commit. It is NOT a substitute for review by a privacy attorney qualified in the jurisdictions Beckon ships in. Update the “last reviewed” date below + this status banner when counsel approves.
Last updated: 2026-05-10 Last reviewed by counsel: not yet
What this document covers
This policy describes what Beckon collects, why, how we protect it, and what control you have over it. It applies to:
- The Beckon iOS app distributed via the App Store + TestFlight
- The
beckon-backendAPI hosted at*.fly.dev(or whatever production hostname ships)
If the policy changes materially, we’ll surface a one-time consent prompt in the app on next launch + email any address you’ve given us. We’ll also keep a changelog at the bottom of this file.
What we collect, and why
Identity (linked to your account)
| What | Why we need it | Where it lives |
|---|---|---|
| Phone number (E.164) | Sign-in via SMS one-time codes; the only way you log in | Server: hashed (SHA-256) + the original is stored encrypted-at-rest by the database |
| Display name + username | Friends find you in their inbox by these labels | Server: plaintext, in the user table |
| Pronouns (optional) | Profile rendering | Server: plaintext, in the user table |
| Bio (optional, ≤280 chars) | Profile rendering | Server: plaintext, in the user table |
| Profile avatar (optional) | Shown on your profile + on the map for friends who can see your home pin | Server: image bytes in the user.avatar_data column. Picked via the iOS PHPicker (OS-mediated; we only see the single image you choose). Server resizes to 512×512, strips EXIF including any embedded GPS / camera serial / timestamp before storing, re-encodes as JPEG capped at 200KB. The original you uploaded is discarded after processing. |
We do not ask for your email, your government name, or any other identity attribute beyond the above.
Per-device identifiers
| What | Why | Where |
|---|---|---|
| Device public keys (X25519 + Ed25519) | Cryptographic identity for the anonymity protocol — see threat model §5 | Server: stored in the device table; private halves never leave your device |
| APNs push token | Sending you “a friend sent you a beacon” notifications | Server: in the device.push_token column; rotated whenever iOS rotates it |
| Refresh-token hash (argon2) | Re-issuing access tokens without re-prompting for SMS | Server only |
Social graph
| What | Why | Visibility |
|---|---|---|
| Friend-request rows | The pending → accepted state machine for adding friends | Visible to the two parties involved |
| Friendship rows | List of confirmed mutual friends | Visible to the two parties involved |
| Block rows | Who you’ve blocked | Visible only to you |
Contact matching (privacy-preserving)
When you tap “find my friends” we ask for iOS Contacts permission. If you grant it:
- We read phone numbers locally on your device
- We SHA-256-hash each number before sending anything to the server
- The server checks each hash against the same hash it computed for the phones of registered users
- The server returns the matching user IDs + relationship state
- The server forgets the submitted hashes within 24 hours
We never receive plaintext phone numbers from your contacts — only hashes. We never auto-send messages or invitations. The hashed contact list is not retained beyond 24 hours; new matches require re-uploading hashes.
If you deny Contacts permission, the rest of the app works
normally. Friends can still find you by sending you a request to
@username.
Location (coarse, voluntary, never precise)
Beckon shows friends’ home pins on a map. The home pin is something you choose — your block, a coffee shop, a neighborhood — not a real-time GPS reading.
When friends look at their map, they see your home with server- side privacy noise added per your chosen precision:
- Exact: real lat/lng (you opt into this)
- Block (~50 m radius): random offset deterministic per (you, that-friend) so they can’t average across many lookups
- Neighborhood (~800 m radius): same model, larger radius — this is the recommended default
- City only: no map pin shown at all, only the city name
The noise is HMAC-derived from a server secret + the (viewer, friend, precision) tuple. Different friends see different noise vectors, so collusion-by-averaging requires k friends colluding to drop noise to ~radius/√k.
Beckon never uses your real-time GPS location. We don’t request CoreLocation in production code paths today. Apple’s MapKit may briefly request location permission to center the map on “where you are” — declining is harmless.
Beacon protocol data
When you organize a beacon (an “anonymous-until-threshold” group ping):
- Server stores opaque encrypted blobs: share envelopes, identity envelopes, share-relay envelopes, signatures. The server cryptographically cannot decrypt these.
- Server learns: who organized the beacon, the audience list (friends to send it to), the threshold, the expiry.
- Server does NOT learn: who said yes (until threshold-hit reveal), the beacon’s plaintext note, or any payload contents.
When threshold is hit:
- The organizer’s app collects k Shamir shares from k yes- responders, locally reconstructs a per-beacon decryption key, and decrypts identity envelopes to learn who said yes.
- Recipients who said NO are never identified to the organizer. Their identity envelopes never get sealed.
Per-beacon ephemeral cryptographic state is deleted after use.
The full protocol — ~1,200 lines of cryptographic detail — is maintained internally and available to qualified researchers on request via privacy@beckonapp.info. It has been internally reviewed but not externally audited. We will commission an independent cryptographic audit before declaring the protocol “production-grade for high-stakes use cases.”
Where data lives, who can read it
- Database: managed Postgres (Fly Postgres) in the US-West region. Encrypted at rest by Fly’s infrastructure.
- iCloud Keychain: per-device key material that needs to survive device wipe is stored synchronizable. Your Apple ID is required to sync; Apple cannot read iCloud Keychain contents.
- APNs: your push token + the generic “a beacon for you” payload routes through Apple’s servers en route to your device. Payloads contain no organizer identity (intentionally generic body — see threat model §5.4).
- Twilio Verify (when production): SMS code delivery. Twilio sees your phone number + the OTP code + the timestamp. Their privacy policy applies in addition to ours for that data flow. Twilio retention is per their defaults; we don’t use Twilio for anything beyond OTP.
- Sentry (optional, can be disabled): exception/error reports. We don’t ship user-identifiable PII to Sentry; we scrub phone-shaped strings + base64 envelope blobs from exception contexts.
We don’t use any third-party analytics SDKs, advertising SDKs, or
tracking libraries. The Beckon iOS app’s privacy manifest
(ios/Beckon/PrivacyInfo.xcprivacy) declares
NSPrivacyTracking = false accordingly.
What we don’t do
- No advertising. We don’t sell, share, or monetize your data. We have no plan to.
- No third-party trackers. No Google Analytics, no Facebook SDK, no Mixpanel, no Amplitude, nothing.
- No selling. This isn’t a hedge — there is no business model premised on selling user data.
- No backups of your private message contents beyond cryptographically encrypted blobs the server cannot decrypt.
Your rights
We honor the following data-subject rights regardless of jurisdiction. Some are explicitly required by GDPR (Articles 15, 16, 17, 18, 20) or CCPA (§1798.100); we apply them universally.
Access (Article 15 GDPR / §1798.110 CCPA)
Use Settings → Export my data in the app. We return a JSON file containing every row in our database that references your account: profile, devices, friend graph, beacons you organized, yes-responses you submitted, etc. The export is self-service + immediate (no email back-and-forth).
Rectification (Article 16 GDPR)
Profile fields (display name, username, pronouns, bio, home pin, precision setting, interests) are editable in the app at any time. Phone number can be changed by deleting the account + re-creating with the new number.
Erasure (Article 17 GDPR / §1798.105 CCPA)
Use Settings → Delete my account in the app. We:
- Soft-delete your
userrow (setsdeleted_at; row stays for 90 days for backup-restore-recovery scenarios, then is hard- deleted on the next monthly purge). - Cascade-delete every row that references your user_id: devices, sessions, OTP attempts, profile data, friendships, friend requests, blocks, home location, beacon membership, yes-response rows.
- Wipe iOS-side state: Keychain (device key + wrapping key + per-beacon org_kex secrets) + UserDefaults + cached data.
After deletion, beacons you organized that already hit threshold remain in friends’ history (the cryptographic protocol means we can’t retroactively redact a name a friend has already learned). Beacons that hadn’t hit threshold yet are server-deleted alongside the account, so neither the organizer-side nor recipient-side state survives.
Portability (Article 20 GDPR)
The same JSON export covers Article 20. Format is documented in the export endpoint.
Restriction / objection (Articles 18–21 GDPR)
Beckon doesn’t profile users, doesn’t run automated decisioning, doesn’t have a marketing pipeline. The only “processing” is what the app does to provide the service. Account deletion is the correct path if you want processing to stop.
Do Not Sell / Share (CCPA / CPRA)
We don’t sell or share data. There is no opt-out to surface
because there is nothing to opt out of. The privacy manifest’s
NSPrivacyTracking = false is the technical realization.
Data retention
| Data | Retention |
|---|---|
| Account + profile | While account is active; 90 days post-deletion for restore-recovery; hard-deleted on next monthly purge |
| Hashed contact matches | 24 hours from upload |
| OTP attempts | 30 days (rate-limit signal); irreversibly hashed phone in the phone_hash column |
| Push tokens | While valid (we drop on APNs Unregistered / BadDeviceToken) |
| Beacon data | Through expires_at (typically 24h post-create). Threshold-hit beacons retain their reveal bundle until expiry; non-threshold beacons drop their identity envelopes on expiry. |
| Merkle audit log roots | Same as parent beacon |
| Server access logs | 30 days, with phone numbers + base64 envelopes scrubbed pre-write |
| Sentry events (if enabled) | Per Sentry’s default 90-day retention |
Children
Beckon is not directed at children under 13 (or under 16 in the EEA). We do not knowingly collect data from minors. If you believe a minor has signed up, contact us via the deletion flow or email below; we will hard-delete the account immediately.
International transfers
Production hosting is in the US (Fly’s sjc region). If you’re
outside the US, your data is processed in the US under the
appropriate transfer mechanism (Standard Contractual Clauses for
EEA users, etc.). Counsel review will pin the final legal basis +
update this section.
Contact
For questions, data-subject requests we can’t fulfill via the in-app flows, or anything else:
- Email: to be set on launch
- Web: to be set on launch
Mailing address: to be set on launch
Changelog
- 2026-05-10 (chapter 29 / bd .8) — initial draft. Author: development team. Status: pending counsel review.