Skip to content

Build a native mobile app

A native iOS or Android app on top of SweatStack has three moving parts that differ from a server-side or web integration:

  1. How the user authenticates from the device.
  2. Where access tokens live
  3. How the app receives real-time updates from SweatStack's webhooks.

This guide covers all three.

What you'll build

An architecture for a native mobile app that:

  • Authenticates users with SweatStack via OAuth2 + PKCE, using the platform's standard system browser session
  • Stores tokens securely on-device and refreshes transparently
  • Receives push notifications when activities or tests change, via a small server-side gateway

1. Authentication on the device

SweatStack uses standard OAuth2 with PKCE for native apps. No SweatStack-specific SDK required on the device. You can wire it up with the OS-provided web auth components or any OAuth2/OIDC library that speaks PKCE.

Platform Auth session Reason
iOS ASWebAuthenticationSession System-managed, shares cookies with Safari, handles redirect dismissal automatically.
Android Custom Tabs (via AppAuth-Android) Same pattern: system browser, cookie-shared, redirect handling.

Both flows are identical from SweatStack's perspective. The device opens an authorization URL, the user authenticates with SweatStack (or via SweatStack Connect using their wearable account), and the redirect URI returns control to your app with an authorization code.

Redirect URI options

Two options work for native apps. Pick one and register it as a redirect URI in your app configuration.

  • Custom URL scheme. Register a scheme like com.example.myapp://oauth/callback. Simpler to set up; works on any iOS or Android version.
  • Universal links (iOS) / App Links (Android). Use a real https:// URL that the OS routes to your app via your domain's apple-app-site-association / assetlinks.json. More setup, but harder to phish and is the modern recommendation for new apps.

Custom URL schemes are fine for a v1. If you have a domain ready and want the better security posture, go with universal links from the start.

Flow

+------------------+                                       +-------------------+
|                  |  1. open ASWebAuthenticationSession   |                   |
|    Native app    | ------------------------------------> |  System browser   |
|                  |     (authorize URL with PKCE)         |                   |
+------------------+                                       +---------+---------+
        ^                                                            |
        |                                                            | 2. user logs in
        |                                                            |    on SweatStack
        |  4. redirect URI returns                                   v
        |     (code + state)                                +-------------------+
        +-------------------------------------------------- |    SweatStack     |
                                                             +-------------------+

+------------------+                                       +-------------------+
|                  |  5. POST /oauth/token (code +         |                   |
|    Native app    |     code_verifier)                    |    SweatStack     |
|                  | ------------------------------------> |                   |
|                  | <------------------------------------ |                   |
|                  |     access_token + refresh_token      |                   |
+------------------+                                       +-------------------+

Important: no client secret on the device

PKCE replaces the client secret. Never ship a client secret in a mobile binary; treat it as a server-only credential. SweatStack's authorization endpoint accepts PKCE without a secret for native flows.

For details on the OAuth2 + PKCE wire format, see the authentication reference.

2. Token storage on the device

Platform Storage
iOS Keychain (e.g. via SecItemAdd or a wrapper like KeychainAccess).
Android EncryptedSharedPreferences or Keystore-backed token store.

Both keep tokens encrypted at rest and tied to the device. Tokens should not be written to plain UserDefaults, files, or SharedPreferences.

Refresh strategy

Access tokens are valid for 15 minutes. Refresh tokens have no expiry until the user revokes the app's permissions. The standard pattern:

  1. Before any API call, check the access token's exp claim (it's a JWT).
  2. If expired or expiring within ~30 seconds, refresh first.
  3. If a request returns 401, refresh and retry once.

A user opening the app once a month after a quiet stretch will refresh successfully without seeing a re-auth screen.

3. Real-time updates: webhooks → push notifications

A phone can't expose a public HTTP endpoint, so it can't receive webhooks directly. The standard pattern is a server-side gateway between SweatStack's webhooks and the device's push channel (APNs on iOS, FCM on Android).

Architecture

+-------------+  webhook   +----------------+  enrich   +-------------+
|             |  POST      |                |  via API  |             |
| SweatStack  | ---------> |  Gateway       | --------> | SweatStack  |
|             |            |  (Worker /     |  (token   |             |
|             |            |   Lambda /     |   lookup) |             |
|             |            |   server)      |           |             |
+-------------+            +--------+-------+           +-------------+
                                    |
                                    | push
                                    v
                            +---------------+
                            |   APNs / FCM  |
                            +-------+-------+
                                    |
                                    v
                            +---------------+
                            |  Native app   |
                            +---------------+

Gateway implementation

A Cloudflare Worker, AWS Lambda, or any small HTTP service works. The gateway:

  1. Receives the webhook from SweatStack. Verifies the HMAC signature (see Webhooks) and acknowledges with 2xx within two seconds.
  2. Looks up the user's access token. Webhook payloads carry user_id, event_type, and resource_id, but no resource data. To enrich the event with details for the push notification ("New ride: 42 km, 1h 30m"), the gateway calls SweatStack with that user's access token. Which means the gateway needs a way to fetch tokens by user_id. A KV store (Cloudflare KV, DynamoDB, your existing user database) keyed on user_id is the standard shape.
  3. Looks up the device's push token. Same lookup, also keyed on user_id.
  4. Sends the push. APNs / FCM with the constructed notification payload.

What goes in the payload

Webhook payload:

{
  "user_id": "string",
  "event_type": "activity_created",
  "resource_id": "string",
  "timestamp": "2026-05-08T12:00:00Z"
}

Useful push notifications usually want richer content: the activity's sport, distance, duration. That's what the enrichment step covers.

Delivery guarantees

SweatStack retries failed webhook deliveries with exponential backoff for up to 24 hours. APNs and FCM also retry. Make the gateway idempotent on (resource_id, event_type) so duplicate deliveries don't produce duplicate pushes.

When a phone is offline, APNs and FCM queue notifications (APNs collapses; FCM stores up to a configurable TTL). The gateway doesn't need to track this; the push services handle it. For the pathological case of a long-offline phone, the app should reconcile state from SweatStack on next foreground rather than relying on every push having arrived.

Background sync as a fallback

For seasonal apps where users open the app intermittently, push-notification-driven sync is the right primary mechanism. Background fetch (BGTaskScheduler on iOS, WorkManager on Android) can be layered on for smoother UX, but the OS controls the scheduling and you can't rely on it for freshness guarantees. Treat it as a "best effort while the app's been backgrounded" mechanism, not as the primary sync.

Next steps

  • Authentication: full OAuth2 + PKCE wire format, token lifecycle, scopes.
  • Webhooks: event types, payload format, signature verification, retry behavior.
  • SweatStack Connect: the productized "Continue with SweatStack" onboarding, which works inside ASWebAuthenticationSession exactly like a regular OAuth2 login.