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:

Loading diagram...

Initialize OAuth

Starts the OAuth flow by redirecting to Amazon's authorization server.

GET /auth/initialize

Query Parameters

ParameterTypeRequiredDescription
statestringYesArbitrary data returned unchanged after OAuth (e.g., session ID)
tenantIdstringYesYour Data Border tenant 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+="&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

StatusMessageCause
400state is requiredMissing state parameter
400tenantId is requiredMissing tenant ID
400Tenant data not foundTenant ID doesn't exist
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 value from initialization (returned unchanged)
tenant_idYour Data Border tenant ID
seller_idData Border seller ID (use this for API calls)
codeClaim 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

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.