OAuth Flow
Amazon authorization flow for seller connections
OAuth Flow
These endpoints handle the Amazon OAuth authorization flow for connecting seller accounts.
Overview
The OAuth flow connects Amazon sellers to your WMS through Data Border:
sequenceDiagram
participant Seller as Seller Browser
participant WMS as Your WMS
participant ADB
participant Amazon
Seller->>WMS: Click "Connect Amazon"
WMS->>Seller: Redirect to ADB OAuth
Seller->>ADB: GET /auth/initialize
ADB->>ADB: Validate, create session
ADB->>Seller: Redirect to Amazon
Seller->>Amazon: Authorize access
Amazon->>ADB: Redirect with auth code
ADB->>Amazon: Exchange code for tokens
ADB->>ADB: Encrypt & store tokens
ADB->>Seller: Redirect to WMS with claim code
Seller->>WMS: Complete callback
WMS->>ADB: POST /api/claim-code
ADB->>WMS: Return seller refresh token
Initialize OAuth
Starts the OAuth flow by redirecting to Amazon's authorization server.
GET /auth/initialize
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
state | string | Yes | Arbitrary data returned after OAuth (e.g., internal seller ID) |
marketplace_region | string | Yes | Amazon region: us-east-1, eu-west-1, or us-west-2 |
amazonTokenSecret | string | Yes | Encryption key for tokens (32-64 chars random base64) |
sellerRedirectUrl | string | Yes | Callback URL (must start with tenant's redirectOrigin) |
Marketplace Regions
| Region | Marketplaces | SP-API Endpoint |
|---|---|---|
us-east-1 | US, Canada, Mexico, Brazil | sellingpartnerapi-na.amazon.com |
eu-west-1 | UK, Germany, France, Italy, Spain, etc. | sellingpartnerapi-eu.amazon.com |
us-west-2 | Japan, Australia, Singapore | sellingpartnerapi-fe.amazon.com |
Response
HTTP 302 redirect to Amazon OAuth consent page.
Example
# Build the OAuth URL
OAUTH_URL="https://adb.example.com/auth/initialize"
OAUTH_URL+="?state=internal-seller-123"
OAUTH_URL+="&marketplace_region=us-east-1"
OAUTH_URL+="&amazonTokenSecret=$(openssl rand -base64 32)"
OAUTH_URL+="&sellerRedirectUrl=https://my-wms.com/oauth/callback"
# Redirect user to this URL
echo $OAUTH_URL
Errors
| Status | Message | Cause |
|---|---|---|
| 400 | state is required | Missing state parameter |
| 400 | marketplace_region is required | Missing or invalid region |
| 400 | amazonTokenSecret must be 32-64 characters | Invalid secret length |
| 400 | sellerRedirectUrl must start with redirectOrigin | URL doesn't match tenant |
| 429 | Rate limit exceeded | More than 5 requests in 10 seconds |
Security Notes
Generate a unique amazonTokenSecret for each seller. This secret encrypts the seller's Amazon tokens. Store it securely alongside the seller's refresh token.
The sellerRedirectUrl is validated against the tenant's configured redirectOrigin:
- If
redirectOriginishttps://my-wms.com/oauth - Valid:
https://my-wms.com/oauth/callback,https://my-wms.com/oauth/done?ref=123 - Invalid:
https://other-site.com/oauth,https://my-wms.com/other
OAuth Redirect Handler
Handles the callback from Amazon after user authorization.
GET /auth/redirect
This endpoint is called by Amazon, not by your WMS directly.
Query Parameters (from Amazon)
| Parameter | Type | Description |
|---|---|---|
state | string | Data Border-generated OAuth state |
selling_partner_id | string | Amazon Selling Partner ID |
spapi_oauth_code | string | Authorization code from Amazon |
Response
HTTP 302 redirect to your sellerRedirectUrl with:
| Parameter | Description |
|---|---|
state | Your original state from initialization |
seller_id | Data Border seller ID (use this for API calls) |
code | Claim code (valid for 5 minutes) |
Example Callback
After successful authorization, the seller is redirected to:
https://my-wms.com/oauth/callback
?state=internal-seller-123
&seller_id=seller_abc123def456
&code=claim_xyz789
Error Callbacks
If authorization fails, the redirect includes error information:
https://my-wms.com/oauth/callback
?state=internal-seller-123
&error=access_denied
&error_description=User+cancelled+authorization
Complete Implementation
Step 1: Generate OAuth URL
const crypto = require('crypto')
function getOAuthUrl(internalSellerId, marketplaceRegion) {
// Generate unique secret for this seller
const amazonTokenSecret = crypto.randomBytes(32).toString('base64')
// Store temporarily (you'll need it when claiming)
cache.set(`oauth:${internalSellerId}:secret`, amazonTokenSecret, 600)
const params = new URLSearchParams({
state: internalSellerId,
marketplace_region: marketplaceRegion,
amazonTokenSecret: amazonTokenSecret,
sellerRedirectUrl: `${process.env.BASE_URL}/oauth/amazon/callback`
})
return `${process.env.ADB_URL}/auth/initialize?${params}`
}
Step 2: Handle Callback
app.get('/oauth/amazon/callback', async (req, res) => {
const { state: internalSellerId, seller_id, code, error } = req.query
// Handle errors
if (error) {
return res.redirect(`/sellers?error=${encodeURIComponent(error)}`)
}
// Get the secret we stored
const amazonTokenSecret = await cache.get(`oauth:${internalSellerId}:secret`)
if (!amazonTokenSecret) {
return res.status(400).send('OAuth session expired')
}
// Claim the authorization
try {
const adbToken = await tokenManager.getAdbAccessToken()
const response = await fetch(`${ADB_URL}/api/claim-code`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-adb-access-token': adbToken
},
body: JSON.stringify({
seller_id,
name: `Seller ${internalSellerId}`,
callback_url: `${process.env.BASE_URL}/webhooks/amazon`,
code
})
})
const data = await response.json()
if (!data.success) {
throw new Error(data.error?.message || 'Failed to claim authorization')
}
// Save seller credentials
await db.sellers.create({
internalId: internalSellerId,
adbSellerId: seller_id,
refreshToken: encrypt(data.data.refresh_token),
amazonTokenSecret: encrypt(amazonTokenSecret),
marketplaceRegion: req.query.marketplace_region
})
// Clean up
await cache.del(`oauth:${internalSellerId}:secret`)
res.redirect('/sellers?connected=true')
} catch (error) {
console.error('OAuth claim failed:', error)
res.redirect(`/sellers?error=${encodeURIComponent(error.message)}`)
}
})
Step 3: Use Seller Credentials
async function callAdbApi(sellerId, endpoint, options = {}) {
const seller = await db.sellers.findOne({ adbSellerId: sellerId })
const { token: accessToken } = await tokenManager.getSellerAccessToken(sellerId)
return fetch(`${ADB_URL}${endpoint}`, {
...options,
headers: {
...options.headers,
'x-seller-access-token': accessToken,
'x-amazon-token-secret': decrypt(seller.amazonTokenSecret)
}
})
}
Rate Limiting
| Endpoint | Limit |
|---|---|
/auth/initialize | 5 requests per 10 seconds per IP |
/auth/redirect | No additional limit (called by Amazon) |
Troubleshooting
"Invalid sellerRedirectUrl"
The redirect URL must start with your tenant's redirectOrigin:
- Check the
redirectOriginyou specified when creating the tenant - Ensure the URL uses HTTPS
- Path can vary, but the origin must match exactly
"OAuth session expired"
The claim code is valid for 5 minutes:
- Claim immediately in your callback handler
- Don't wait for user input before claiming
- If expired, the user must re-authorize
"Invalid amazonTokenSecret"
The secret must be:
- 32-64 characters long
- Base64 encoded random data
- The same secret used during initialization and claiming
