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 state, tenant_id, seller_id, code
Seller->>WMS: Complete callback
WMS->>ADB: POST /api/claim-code
ADB->>WMS: Return seller refresh tokenInitialize 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 unchanged after OAuth (e.g., session ID) |
tenantId | string | Yes | Your Data Border tenant 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+="&tenantId=clx1y2z3a4b5c6d7e8f9g0h1"
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 | tenantId is required | Missing tenant ID |
| 400 | Tenant data not found | Tenant ID doesn't exist |
| 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 value from initialization (returned unchanged) |
tenant_id | Your Data Border tenant ID |
seller_id | Data Border seller ID (use this for API calls) |
code | Claim code (valid for 5 minutes) |
Seller re-registration: If a seller with the same Amazon Selling Partner ID already exists for the given tenant and marketplace region, the existing seller record is reused and its tokens are updated. This allows sellers to re-authorize without creating duplicates.
Example Callback
After successful authorization, the seller is redirected to:
https://my-wms.com/oauth/callback
?state=internal-seller-123
&tenant_id=clx1y2z3a4b5c6d7e8f9g0h1
&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
&tenant_id=clx1y2z3a4b5c6d7e8f9g0h1
&error=access_denied
&error_description=User+cancelled+authorization
Complete Implementation
Step 1: Generate OAuth URL
const crypto = require('crypto')
function getOAuthUrl(tenantId, 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,
tenantId: tenantId,
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, tenant_id, 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
