Skip to main content
GET
/
api
/
v1
/
oauth
/
{provider}
/
callback
OAuth Callback
curl --request GET \
  --url https://api.example.com/api/v1/oauth/{provider}/callback
{
  "success": true,
  "message": "<string>",
  "user_id": "<string>",
  "provider": "<string>"
}

Overview

This endpoint is called by OAuth providers after the user authorizes access. It exchanges the authorization code for access and refresh tokens, stores the connection, and triggers an initial data sync.
This endpoint is designed to be called by OAuth providers, not directly by your application. Users are redirected here automatically after authorizing on the provider’s site.

Path Parameters

provider
string
required
Provider identifier (e.g., garmin, polar, strava, oura)

Query Parameters

code
string
required
Authorization code from the OAuth provider. This is a one-time code that will be exchanged for access tokens.
state
string
required
State parameter for CSRF protection. Must match the state generated in the authorize request.
error
string
Error code if user denied access or authorization failed
error_description
string
Human-readable description of the error

Response

This endpoint returns a redirect response (HTTP 303 See Other), not JSON:

Success Redirect

If a custom redirect_uri was provided during authorization:
HTTP/1.1 303 See Other
Location: https://your-app.com/success?connected=true
If no custom redirect URI was provided:
HTTP/1.1 303 See Other
Location: /api/v1/oauth/success?provider=garmin&user_id=123e4567-e89b-12d3-a456-426614174000

Error Redirect

If authorization fails:
HTTP/1.1 303 See Other
Location: /api/v1/oauth/error?message=access_denied:+User+denied+authorization

Success Response Schema

When redirected to /api/v1/oauth/success, the response is:
success
boolean
Always true for successful connections
message
string
Success message (e.g., “Successfully connected to garmin”)
user_id
string
UUID of the connected user
provider
string
Provider identifier

Example Success Response

{
  "success": true,
  "message": "Successfully connected to garmin",
  "user_id": "123e4567-e89b-12d3-a456-426614174000",
  "provider": "garmin"
}

Error Response Schema

When redirected to /api/v1/oauth/error, the response is:
success
boolean
Always false for errors
message
string
Error description

Example Error Response

{
  "success": false,
  "message": "access_denied: User denied authorization"
}

Callback Flow

1

Validate parameters

Endpoint checks for error parameter or missing code/state
2

Verify state

State parameter is validated against stored value in Redis to prevent CSRF attacks
3

Exchange code

Authorization code is exchanged for access and refresh tokens via provider’s token endpoint
4

Store connection

User connection is created/updated in database with encrypted tokens
5

Trigger sync

Background Celery task is queued to sync user’s health data
6

Redirect user

User is redirected to success page or custom redirect URI

Background Processing

After successful callback:

Standard Data Sync

A sync_vendor_data Celery task is automatically queued to fetch the user’s latest health data:
sync_vendor_data.delay(
    user_id=str(user_id),
    start_date=None,  # Uses provider's default lookback period
    end_date=None,
    providers=[provider]
)

Garmin Backfill

For Garmin connections, an additional 30-day backfill task is triggered:
start_garmin_full_backfill.delay(str(user_id))
This fetches historical data for all supported Garmin data types.

Common Error Scenarios

User Denied Access

Provider redirects with error parameter:
/api/v1/oauth/garmin/callback?error=access_denied&error_description=User+denied+authorization

Invalid State (CSRF)

If state doesn’t match or has expired, the callback fails with a 400 error.

Missing Parameters

If code or state is missing:
/api/v1/oauth/error?message=Missing+OAuth+parameters

Token Exchange Failure

If the provider’s token endpoint returns an error, it will be logged and an error page shown.

Implementation Example

While this endpoint is called by providers, here’s how you’d set up the full OAuth flow:
import httpx
import webbrowser

# Step 1: Get authorization URL
auth_response = httpx.get(
    "https://api.openwearables.com/api/v1/oauth/garmin/authorize",
    params={"user_id": "123e4567-e89b-12d3-a456-426614174000"},
    headers={"Authorization": "Bearer YOUR_API_KEY"}
)

auth_data = auth_response.json()
authorization_url = auth_data["authorization_url"]

# Step 2: Open browser for user to authorize
webbrowser.open(authorization_url)

# Step 3: Provider calls callback endpoint automatically
# Step 4: User is redirected to success page
print("User will be redirected after authorization")

Security Considerations

  • State Validation: The state parameter prevents CSRF attacks by ensuring the callback matches an initiated authorization flow
  • Token Storage: Access and refresh tokens are encrypted before storage in the database
  • Token Expiration: Token expiration times are tracked and tokens are automatically refreshed when needed
  • HTTPS Only: OAuth flows must use HTTPS in production to prevent token interception

Notes

  • The callback URL must be registered with each OAuth provider in their developer console
  • State tokens expire after 10 minutes if not used
  • Some providers (like Garmin) may take several seconds to process the token exchange
  • The data sync happens asynchronously in the background and may take several minutes depending on data volume

Build docs developers (and LLMs) love