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

ParameterTypeRequiredDescription
statestringYesArbitrary data returned after OAuth (e.g., internal seller ID)
marketplace_regionstringYesAmazon region: us-east-1, eu-west-1, or us-west-2
amazonTokenSecretstringYesEncryption key for tokens (32-64 chars random base64)
sellerRedirectUrlstringYesCallback URL (must start with tenant's redirectOrigin)

Marketplace Regions

RegionMarketplacesSP-API Endpoint
us-east-1US, Canada, Mexico, Brazilsellingpartnerapi-na.amazon.com
eu-west-1UK, Germany, France, Italy, Spain, etc.sellingpartnerapi-eu.amazon.com
us-west-2Japan, Australia, Singaporesellingpartnerapi-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

StatusMessageCause
400state is requiredMissing state parameter
400marketplace_region is requiredMissing or invalid region
400amazonTokenSecret must be 32-64 charactersInvalid secret length
400sellerRedirectUrl must start with redirectOriginURL doesn't match tenant
429Rate limit exceededMore 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 redirectOrigin is https://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)

ParameterTypeDescription
statestringData Border-generated OAuth state
selling_partner_idstringAmazon Selling Partner ID
spapi_oauth_codestringAuthorization code from Amazon

Response

HTTP 302 redirect to your sellerRedirectUrl with:

ParameterDescription
stateYour original state from initialization
seller_idData Border seller ID (use this for API calls)
codeClaim 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

EndpointLimit
/auth/initialize5 requests per 10 seconds per IP
/auth/redirectNo additional limit (called by Amazon)

Troubleshooting

"Invalid sellerRedirectUrl"

The redirect URL must start with your tenant's redirectOrigin:

  • Check the redirectOrigin you 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

Next Steps

Seller Management

Manage seller tokens after OAuth.

Passthrough API

Make SP-API calls through Data Border.