DEV Community

Cover image for Hướng Dẫn Từng Bước Tích Hợp Amazon SP API
Sebastian Petrus
Sebastian Petrus

Posted on • Originally published at apidog.com

Hướng Dẫn Từng Bước Tích Hợp Amazon SP API

TL;DR

Amazon Selling Partner API (SP-API) là API REST cấp quyền truy cập dữ liệu người bán (đơn hàng, tồn kho, danh sách sản phẩm, thực hiện đơn hàng). Xác thực bằng OAuth 2.0 + IAM, yêu cầu ký AWS SigV4, giới hạn tốc độ tùy điểm cuối (0.1–100 req/s). Bài này hướng dẫn chi tiết thiết lập tài khoản, xác thực, gọi API cốt lõi, đăng ký webhook và triển khai sản xuất tối ưu.

Dùng thử Apidog ngay hôm nay

💡 Apidog giúp kiểm thử tích hợp API dễ dàng: xác thực luồng OAuth, kiểm tra chữ ký SigV4, debug xác thực, nhập spec API, tạo mock response và chia sẻ test với nhóm.


Amazon SP-API là gì?

Amazon Selling Partner API (SP-API) là API REST truy cập dữ liệu người bán, thay thế cho MWS với bảo mật và hiệu suất vượt trội.

Các khả năng chính

  • Truy xuất và cập nhật đơn hàng
  • Quản lý tồn kho đa thị trường
  • Tạo/cập nhật/xóa danh sách sản phẩm
  • Quản lý vận chuyển FBA
  • Định giá, phân tích cạnh tranh, báo cáo
  • Quản lý nội dung A+
  • Dữ liệu quảng cáo & phân tích thương hiệu

SP-API vs. MWS

Tính năng SP-API MWS (Cũ)
Kiến trúc RESTful JSON XML
Xác thực OAuth 2.0 + IAM MWS Auth Token
Bảo mật AWS SigV4 Token đơn giản
Giới hạn tốc độ Theo điểm cuối Hạn ngạch cố định
Thị trường Thống nhất Theo khu vực
Trạng thái Hiện hành Sắp loại bỏ 12/2025

Hành động: Di chuyển các tích hợp MWS sang SP-API trước tháng 12/2025.

Tổng quan kiến trúc API

  • API phân vùng theo khu vực:
  https://sellingpartnerapi-na.amazon.com (Bắc Mỹ)
  https://sellingpartnerapi-eu.amazon.com (Châu Âu)
  https://sellingpartnerapi-fe.amazon.com (Viễn Đông)
Enter fullscreen mode Exit fullscreen mode
  • Mọi request cần:
    1. Chữ ký SigV4
    2. Access token OAuth
    3. Quyền IAM phù hợp
    4. Request ID

Thị trường được hỗ trợ

Khu vực Thị trường Điểm cuối API
Bắc Mỹ US, CA, MX sellingpartnerapi-na.amazon.com
Châu Âu UK, DE, FR... sellingpartnerapi-eu.amazon.com
Viễn Đông JP, AU, SG, BR sellingpartnerapi-fe.amazon.com

Thiết lập tài khoản & IAM

Bước 1: Tạo tài khoản nhà phát triển

  1. Vào Amazon Developer Central
  2. Đăng nhập bằng tài khoản có quyền Seller Central
  3. Vào Selling Partner API và chấp nhận thỏa thuận

Bước 2: Đăng ký ứng dụng

  1. Đăng nhập Seller Central
  2. Vào Apps and Services > Develop Apps
  3. Nhấn Add New App và khai báo:
    • Tên ứng dụng
    • Loại ứng dụng (“Tự phát triển” hoặc “Bên thứ ba”)
    • Mô tả use case
    • Redirect URI (HTTPS)
  4. Nhận:
    • Application ID
    • Client ID
    • Client Secret

Bảo mật: Lưu thông tin xác thực vào file .env – không push code lên repo!

# .env
AMAZON_APPLICATION_ID="amzn1.application.xxxxx"
AMAZON_CLIENT_ID="amzn1.account.xxxxx"
AMAZON_CLIENT_SECRET="your_client_secret_here"
AMAZON_SELLER_ID="your_seller_id_here"
AWS_ACCESS_KEY_ID="your_aws_access_key"
AWS_SECRET_ACCESS_KEY="your_aws_secret_key"
AWS_REGION="us-east-1"
Enter fullscreen mode Exit fullscreen mode

Bước 3: Tạo vai trò IAM cho SP-API

  1. Đăng nhập AWS IAM
  2. Vào Roles > Create Role
  3. Chọn Another AWS account và nhập Account ID Amazon theo khu vực:
    • Bắc Mỹ: 906394416454
    • Châu Âu: 336853085554
    • Viễn Đông: 774466381866

Bước 4: Gán policy IAM

Gán policy sau cho role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "execute-api:Invoke"
      ],
      "Resource": [
        "arn:aws:execute-api:*:*:*/prod/*/sellingpartnerapi/*"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Ghi lại ARN role vừa tạo.

Bước 5: Liên kết IAM Role với ứng dụng

  1. Vào Seller Central > Develop Apps
  2. Chọn app > Edit > IAM Role ARN
  3. Nhập ARN vừa tạo, lưu lại

Luồng xác thực OAuth 2.0

Tổng quan OAuth SP-API

Luồng xác thực chuẩn:

  1. Người bán nhấn Authorize trên ứng dụng của bạn
  2. Chuyển hướng đến URL OAuth Amazon
  3. Đăng nhập, cấp quyền
  4. Amazon redirect về với authorization code
  5. Đổi code lấy LWA token
  6. Đổi LWA token lấy SP-API access token
  7. Gọi API (phải ký SigV4)
  8. Tự động refresh token khi hết hạn (1h)

Bước 6: Tạo URL ủy quyền OAuth

const generateAuthUrl = (clientId, redirectUri, state) => {
  const baseUrl = 'https://www.amazon.com/sp/apps/oauth/authorize';
  const params = new URLSearchParams({
    application_id: process.env.AMAZON_APPLICATION_ID,
    client_id: clientId,
    redirect_uri: redirectUri,
    state: state,
    scope: 'sellingpartnerapi::notifications'
  });
  return `${baseUrl}?${params.toString()}`;
};

// Sử dụng
const authUrl = generateAuthUrl(
  process.env.AMAZON_CLIENT_ID,
  'https://your-app.com/callback',
  crypto.randomBytes(16).toString('hex')
);
console.log(`Redirect user to: ${authUrl}`);
Enter fullscreen mode Exit fullscreen mode

Các scope OAuth chính

Scope Mô tả Dùng cho
sellingpartnerapi::notifications Nhận noti Đăng ký webhook
sellingpartnerapi::migration Di chuyển MWS Tích hợp kế thừa

Phần lớn quyền truy cập kiểm soát qua IAM policy.

Bước 7: Đổi code lấy LWA token

const exchangeCodeForLwaToken = async (code, redirectUri) => {
  const response = await fetch('https://api.amazon.com/auth/o2/token', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: process.env.AMAZON_CLIENT_ID,
      client_secret: process.env.AMAZON_CLIENT_SECRET,
      redirect_uri: redirectUri,
      code: code
    })
  });
  const data = await response.json();
  return {
    access_token: data.access_token,
    refresh_token: data.refresh_token,
    expires_in: data.expires_in,
    token_type: data.token_type
  };
};

// Xử lý callback OAuth
app.get('/callback', async (req, res) => {
  const { spapi_oauth_code, state } = req.query;
  if (state !== req.session.oauthState) return res.status(400).send('Invalid state');
  try {
    const tokens = await exchangeCodeForLwaToken(spapi_oauth_code, 'https://your-app.com/callback');
    await db.sellers.update(req.session.sellerId, {
      amazon_lwa_access_token: tokens.access_token,
      amazon_lwa_refresh_token: tokens.refresh_token,
      amazon_token_expires: Date.now() + (tokens.expires_in * 1000)
    });
    res.redirect('/dashboard');
  } catch (e) {
    res.status(500).send('Authentication failed');
  }
});
Enter fullscreen mode Exit fullscreen mode

Bước 8: Đổi LWA token lấy credential SP-API

const assumeRole = async (lwaAccessToken) => {
  const response = await fetch('https://api.amazon.com/auth/o2/token', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: process.env.AMAZON_CLIENT_ID,
      client_secret: process.env.AMAZON_CLIENT_SECRET,
      scope: 'sellingpartnerapi::notifications'
    })
  });
  const data = await response.json();

  // Đổi lấy credential AWS STS
  const stsResponse = await fetch('https://sts.amazonaws.com/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Bearer ${data.access_token}`
    },
    body: new URLSearchParams({
      Action: 'AssumeRole',
      RoleArn: 'arn:aws:iam::YOUR_ACCOUNT:role/SellingPartnerApiRole',
      RoleSessionName: 'sp-api-session',
      Version: '2011-06-15'
    })
  });

  return stsResponse;
};
Enter fullscreen mode Exit fullscreen mode

Bước 9: Tự động refresh token

const refreshLwaToken = async (refreshToken) => {
  const response = await fetch('https://api.amazon.com/auth/o2/token', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: process.env.AMAZON_CLIENT_ID,
      client_secret: process.env.AMAZON_CLIENT_SECRET,
      refresh_token: refreshToken
    })
  });
  const data = await response.json();
  return {
    access_token: data.access_token,
    refresh_token: data.refresh_token,
    expires_in: data.expires_in
  };
};

// Middleware đảm bảo token hợp lệ
const ensureValidToken = async (sellerId) => {
  const seller = await db.sellers.findById(sellerId);
  if (seller.amazon_token_expires < Date.now() + 300000) {
    const newTokens = await refreshLwaToken(seller.amazon_lwa_refresh_token);
    await db.sellers.update(sellerId, {
      amazon_lwa_access_token: newTokens.access_token,
      amazon_lwa_refresh_token: newTokens.refresh_token,
      amazon_token_expires: Date.now() + (newTokens.expires_in * 1000)
    });
    return newTokens.access_token;
  }
  return seller.amazon_lwa_access_token;
};
Enter fullscreen mode Exit fullscreen mode

Ký yêu cầu AWS SigV4

Cách ký SigV4 thủ công

const crypto = require('crypto');
class SigV4Signer {
  constructor(accessKey, secretKey, region, service = 'execute-api') {
    this.accessKey = accessKey;
    this.secretKey = secretKey;
    this.region = region;
    this.service = service;
  }
  sign(method, url, body = '', headers = {}) {
    const parsedUrl = new URL(url);
    const now = new Date();
    const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
    const dateStamp = amzDate.slice(0, 8);
    headers['host'] = parsedUrl.host;
    headers['x-amz-date'] = amzDate;
    headers['x-amz-access-token'] = this.accessToken;
    headers['content-type'] = 'application/json';
    const canonicalHeaders = Object.entries(headers)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([k, v]) => `${k.toLowerCase()}:${v.trim()}`)
      .join('\n');
    const signedHeaders = Object.keys(headers)
      .sort().map(k => k.toLowerCase()).join(';');
    const payloadHash = crypto.createHash('sha256').update(body).digest('hex');
    const canonicalRequest = [
      method.toUpperCase(),
      parsedUrl.pathname,
      parsedUrl.search.slice(1),
      canonicalHeaders,
      '',
      signedHeaders,
      payloadHash
    ].join('\n');
    const algorithm = 'AWS4-HMAC-SHA256';
    const credentialScope = `${dateStamp}/${this.region}/${this.service}/aws4_request`;
    const stringToSign = [
      algorithm,
      amzDate,
      credentialScope,
      crypto.createHash('sha256').update(canonicalRequest).digest('hex')
    ].join('\n');
    const kDate = this.hmac(`AWS4${this.secretKey}`, dateStamp);
    const kRegion = this.hmac(kDate, this.region);
    const kService = this.hmac(kRegion, this.service);
    const kSigning = this.hmac(kService, 'aws4_request');
    const signature = this.hmac(kSigning, stringToSign, 'hex');
    const authorization = `${algorithm} Credential=${this.accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
    return {
      headers: {
        ...headers,
        'Authorization': authorization
      },
      canonicalRequest,
      stringToSign,
      signature
    };
  }
  hmac(key, data, encoding = 'buffer') {
    return crypto.createHmac('sha256', key).update(data).digest(encoding);
  }
}
Enter fullscreen mode Exit fullscreen mode

Khuyến nghị: Sử dụng AWS SDK để đơn giản hóa ký SigV4.

const { SignatureV4 } = require('@aws-sdk/signature-v4');
const { Sha256 } = require('@aws-crypto/sha256-js');

const signer = new SignatureV4({
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  },
  region: 'us-east-1',
  service: 'execute-api',
  sha256: Sha256
});

const makeSpApiRequest = async (method, endpoint, accessToken, body = null) => {
  const url = new URL(endpoint);
  const headers = {
    'host': url.host,
    'content-type': 'application/json',
    'x-amz-access-token': accessToken,
    'x-amz-date': new Date().toISOString().replace(/[:-]|\.\d{3}/g, '')
  };
  const signedRequest = await signer.sign({
    method,
    hostname: url.hostname,
    path: url.pathname,
    query: Object.fromEntries(url.searchParams),
    headers,
    body: body ? JSON.stringify(body) : undefined
  });
  const response = await fetch(endpoint, {
    method,
    headers: signedRequest.headers,
    body: signedRequest.body
  });
  if (!response.ok) {
    const error = await response.json();
    throw new Error(`SP-API Error: ${error.errors?.[0]?.message || response.statusText}`);
  }
  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

API Đơn hàng

Truy xuất đơn hàng

const getOrders = async (accessToken, options = {}) => {
  const params = new URLSearchParams({
    createdAfter: options.createdAfter,
    createdBefore: options.createdBefore,
    orderStatuses: options.orderStatuses?.join(',') || '',
    marketplaceIds: options.marketplaceIds?.join(',') || ['ATVPDKIKX0DER'],
    maxResultsPerPage: options.maxResultsPerPage || 100
  });
  for (const [key, value] of params.entries()) {
    if (!value) params.delete(key);
  }
  const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders?${params.toString()}`;
  return makeSpApiRequest('GET', endpoint, accessToken);
};
Enter fullscreen mode Exit fullscreen mode

Lấy mặt hàng trong đơn hàng

const getOrderItems = async (accessToken, orderId) => {
  const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders/${orderId}/orderItems`;
  return makeSpApiRequest('GET', endpoint, accessToken);
};
Enter fullscreen mode Exit fullscreen mode

Xác nhận vận chuyển

const confirmShipment = async (accessToken, orderId, shipmentData) => {
  const endpoint = `https://sellingpartnerapi-na.amazon.com/orders/v0/orders/${orderId}/shipmentConfirmation`;
  const payload = {
    packageDetails: {
      packageReferenceId: shipmentData.packageReferenceId || '1',
      carrier_code: shipmentData.carrierCode,
      tracking_number: shipmentData.trackingNumber,
      ship_date: shipmentData.shipDate || new Date().toISOString(),
      items: shipmentData.items.map(item => ({
        order_item_id: item.orderItemId,
        quantity: item.quantity
      }))
    }
  };
  return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
Enter fullscreen mode Exit fullscreen mode

API Tồn kho

Lấy tồn kho

const getInventorySummaries = async (accessToken, options = {}) => {
  const params = new URLSearchParams({
    granularityType: options.granularityType || 'Marketplace',
    granularityId: options.granularityId || 'ATVPDKIKX0DER',
    startDateTime: options.startDateTime || '',
    sellerSkus: options.sellerSkus?.join(',') || ''
  });
  const endpoint = `https://sellingpartnerapi-na.amazon.com/fba/inventory/v1/summaries?${params.toString()}`;
  return makeSpApiRequest('GET', endpoint, accessToken);
};
Enter fullscreen mode Exit fullscreen mode

Tạo kế hoạch vận chuyển FBA

const createInboundShipmentPlan = async (accessToken, shipmentData) => {
  const endpoint = 'https://sellingpartnerapi-na.amazon.com/fba/inbound/v0/plans';
  const payload = {
    ShipFromAddress: {
      Name: shipmentData.shipFromName,
      AddressLine1: shipmentData.shipFromAddress,
      City: shipmentData.shipFromCity,
      StateOrProvinceCode: shipmentData.shipFromState,
      CountryCode: shipmentData.shipFromCountry,
      PostalCode: shipmentData.shipFromPostalCode
    },
    LabelPrepPreference: 'SELLER_LABEL',
    InboundPlanItems: shipmentData.items.map(item => ({
      SellerSKU: item.sku,
      ASIN: item.asin,
      Quantity: item.quantity,
      Condition: 'NewItem'
    }))
  };
  return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
Enter fullscreen mode Exit fullscreen mode

API Danh sách sản phẩm

Lấy danh sách sản phẩm

const getListings = async (accessToken, options = {}) => {
  const params = new URLSearchParams({
    marketplaceIds: options.marketplaceIds?.join(',') || ['ATVPDKIKX0DER'],
    itemTypes: options.itemTypes?.join(',') || ['ASIN', 'SKU'],
    identifiers: options.identifiers?.join(',') || '',
    issuesLocale: options.locale || 'en_US'
  });
  const endpoint = `https://sellingpartnerapi-na.amazon.com/listings/2021-08-01/items?${params.toString()}`;
  return makeSpApiRequest('GET', endpoint, accessToken);
};
Enter fullscreen mode Exit fullscreen mode

Cập nhật danh sách sản phẩm

const submitListingUpdate = async (accessToken, listingData) => {
  const endpoint = 'https://sellingpartnerapi-na.amazon.com/listings/2021-08-01/items/MYSKU-001';
  const payload = {
    productType: 'LUGGAGE',
    patches: [
      {
        op: 'replace',
        path: '/attributes/title',
        value: 'Updated Wireless Bluetooth Headphones - Premium Sound'
      },
      {
        op: 'replace',
        path: '/salesPrice',
        value: {
          currencyCode: 'USD',
          amount: '79.99'
        }
      }
    ]
  };
  return makeSpApiRequest('PATCH', endpoint, accessToken, payload);
};
Enter fullscreen mode Exit fullscreen mode

Xóa danh sách sản phẩm

const deleteListing = async (accessToken, sku, marketplaceIds) => {
  const params = new URLSearchParams({
    marketplaceIds: marketplaceIds.join(',')
  });
  const endpoint = `https://sellingpartnerapi-na.amazon.com/listings/2021-08-01/items/${sku}?${params.toString()}`;
  return makeSpApiRequest('DELETE', endpoint, accessToken);
};
Enter fullscreen mode Exit fullscreen mode

API Báo cáo

Tạo báo cáo

const createReport = async (accessToken, reportType, dateRange) => {
  const endpoint = 'https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/reports';
  const payload = {
    reportType: reportType,
    marketplaceIds: dateRange.marketplaceIds || ['ATVPDKIKX0DER'],
    dataStartTime: dateRange.startTime?.toISOString(),
    dataEndTime: dateRange.endTime?.toISOString()
  };
  return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
Enter fullscreen mode Exit fullscreen mode

Lấy tài liệu báo cáo

const getReportDocument = async (accessToken, reportId) => {
  const endpoint = `https://sellingpartnerapi-na.amazon.com/reports/2021-06-30/reports/${reportId}/document`;
  return makeSpApiRequest('GET', endpoint, accessToken);
};

const downloadReport = async (accessToken, reportId) => {
  const documentInfo = await getReportDocument(accessToken, reportId);
  const response = await fetch(documentInfo.payload.url);
  const content = await response.text();
  if (documentInfo.payload.compressionAlgorithm === 'GZIP') {
    const decompressed = await decompressGzip(content);
    return decompressed;
  }
  return content;
};
Enter fullscreen mode Exit fullscreen mode

API Thông báo

Đăng ký webhook (SNS)

const createSubscription = async (accessToken, subscriptionData) => {
  const endpoint = 'https://sellingpartnerapi-na.amazon.com/notifications/v1/subscriptions';
  const payload = {
    payload: {
      destination: {
        resource: subscriptionData.destinationArn,
        name: subscriptionData.name
      },
      modelVersion: '1.0',
      eventFilter: {
        eventCode: subscriptionData.eventCode,
        marketplaceIds: subscriptionData.marketplaceIds
      }
    }
  };
  return makeSpApiRequest('POST', endpoint, accessToken, payload);
};
Enter fullscreen mode Exit fullscreen mode

Tạo đích SNS

const createSnsDestination = async (accessToken, destinationData) => {
  const endpoint = 'https://sellingpartnerapi-na.amazon.com/notifications/v1/destinations';
  const payload = {
    resource: destinationData.snsTopicArn,
    name: destinationData.name
  };
  return makeSpApiRequest('POST', endpoint, accessToken, { payload });
};
Enter fullscreen mode Exit fullscreen mode

Xử lý thông báo SNS

const express = require('express');
const app = express();

app.post('/webhooks/amazon', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-amz-sns-message-signature'];
  const payload = req.body;
  // Xác minh chữ ký SNS (implement verifySnsSignature)
  const isValid = await verifySnsSignature(payload, signature);
  if (!isValid) return res.status(401).send('Unauthorized');
  const message = JSON.parse(payload.toString());
  switch (message.Type) {
    case 'SubscriptionConfirmation':
      await fetch(message.SubscribeURL);
      break;
    case 'Notification':
      const notification = JSON.parse(message.Message);
      await handleSpApiNotification(notification);
      break;
  }
  res.status(200).send('OK');
});
Enter fullscreen mode Exit fullscreen mode

Giới hạn tốc độ và xử lý rate limit

Rate limit điển hình

Điểm cuối Limit (req/s) Burst
Đơn hàng 10 20
Mặt hàng ĐH 5 10
Tồn kho 2 5
Danh sách SP 10 20
Báo cáo 0.5 1
Thông báo 1 2
FBA Inbound 2 5

Theo dõi header x-amzn-RateLimit-Limit để biết limit thực tế.

Retry với exponential backoff

const makeRateLimitedRequest = async (method, endpoint, accessToken, body = null, maxRetries = 5) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await makeSpApiRequest(method, endpoint, accessToken, body);
      const rateLimit = response.headers.get('x-amzn-RateLimit-Limit');
      const retryAfter = response.headers.get('Retry-After');
      if (retryAfter) {
        console.warn(`Đã đạt giới hạn tốc độ. Thử lại sau: ${retryAfter} giây`);
      }
      return response;
    } catch (error) {
      if (error.message.includes('429') && attempt < maxRetries) {
        const retryAfter = error.headers?.get('Retry-After') || Math.pow(2, attempt);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      } else if (error.message.includes('503') && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Queue xử lý request giới hạn tốc độ

class RateLimitedQueue {
  constructor(rateLimit, burstLimit = null) {
    this.rateLimit = rateLimit;
    this.burstLimit = burstLimit || rateLimit * 2;
    this.tokens = this.burstLimit;
    this.lastRefill = Date.now();
    this.queue = [];
    this.processing = false;
  }
  async add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this.process();
    });
  }
  refillTokens() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    const tokensToAdd = elapsed * this.rateLimit;
    this.tokens = Math.min(this.burstLimit, this.tokens + tokensToAdd);
    this.lastRefill = now;
  }
  async process() {
    if (this.processing || this.queue.length === 0) return;
    this.processing = true;
    while (this.queue.length > 0) {
      this.refillTokens();
      if (this.tokens < 1) {
        const waitTime = (1 / this.rateLimit) * 1000;
        await new Promise(r => setTimeout(r, waitTime));
        continue;
      }
      this.tokens--;
      const { requestFn, resolve, reject } = this.queue.shift();
      try {
        const result = await requestFn();
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }
    this.processing = false;
  }
}
// Sử dụng
const ordersQueue = new RateLimitedQueue(10, 20);
const orders = await ordersQueue.add(() => getOrders(accessToken, options));
Enter fullscreen mode Exit fullscreen mode

Bảo mật và lưu trữ thông tin xác thực

  • Không hardcode credential trong code, dùng biến môi trường hoặc AWS Secrets Manager
  • Mã hóa token khi lưu trữ (AES-256)
  • Chỉ cấp quyền tối thiểu cần thiết với IAM Policy
  • Đảm bảo request luôn qua HTTPS/TLS
  • Log mọi sự kiện làm mới/lấy token
class TokenStore {
  constructor(encryptionKey) {
    this.algorithm = 'aes-256-gcm';
    this.key = crypto.createHash('sha256').update(encryptionKey).digest('hex').slice(0, 32);
  }
  encrypt(token) {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv(this.algorithm, Buffer.from(this.key), iv);
    let encrypted = cipher.update(token, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    const authTag = cipher.getAuthTag().toString('hex');
    return {
      iv: iv.toString('hex'),
      encryptedData: encrypted,
      authTag
    };
  }
  decrypt(encryptedData) {
    const decipher = crypto.createDecipheriv(
      this.algorithm,
      Buffer.from(this.key),
      Buffer.from(encryptedData.iv, 'hex')
    );
    decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
    let decrypted = decipher.update(encryptedData.encryptedData, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
  }
}
Enter fullscreen mode Exit fullscreen mode

Kiểm thử tích hợp SP-API với Apidog

Tại sao dùng Apidog kiểm thử SP-API?

  • Xác thực OAuth 2.0
  • Gỡ lỗi ký SigV4
  • Test xử lý lỗi rate limit
  • Tạo mock API nhanh
  • Tài liệu hóa workflow API

Thiết lập Apidog kiểm thử SP-API

Bước 1: Nhập OpenAPI spec (tham khảo tại đây)

Bước 2: Cấu hình biến môi trường:

Base URL: https://sandbox.sellingpartnerapi-na.amazon.com
LWA Access Token: {{lwa_access_token}}
AWS Access Key: {{aws_access_key}}
AWS Secret Key: {{aws_secret_key}}
Region: us-east-1
Enter fullscreen mode Exit fullscreen mode

Bước 3: Dùng script pre-request để ký SigV4:

const crypto = require('crypto');
const accessKey = apidog.variables.get('aws_access_key');
const secretKey = apidog.variables.get('aws_secret_key');
const accessToken = apidog.variables.get('lwa_access_token');
const region = apidog.variables.get('region');
const method = apidog.request.method;
const url = new URL(apidog.request.url);
const body = apidog.request.body;
const signer = new SigV4Signer(accessKey, secretKey, region);
const signedHeaders = signer.sign(method, url.href, body, {
  'x-amz-access-token': accessToken
});
apidog.request.headers = {
  ...apidog.request.headers,
  ...signedHeaders.headers
};
Enter fullscreen mode Exit fullscreen mode

Bước 4: Xây dựng kịch bản kiểm thử:

const ordersResponse = await apidog.send({
  method: 'GET',
  url: '/orders/v0/orders',
  params: {
    createdAfter: new Date(Date.now() - 86400000).toISOString(),
    marketplaceIds: 'ATVPDKIKX0DER'
  }
});
apidog.assert(ordersResponse.status === 200, 'Yêu cầu đơn hàng thất bại');
apidog.assert(ordersResponse.data.payload.orders.length > 0, 'Không tìm thấy đơn hàng');
const orderIds = ordersResponse.data.payload.orders.map(o => o.amazon_order_id);
apidog.variables.set('order_ids', JSON.stringify(orderIds));
Enter fullscreen mode Exit fullscreen mode

Mock response:

{
  "payload": {
    "orders": [
      {
        "amazon_order_id": "112-{{randomNumber}}-{{randomNumber}}",
        "order_status": "Unshipped",
        "purchase_date": "{{now}}",
        "order_total": {
          "currency_code": "USD",
          "amount": "{{randomFloat 10 500}}"
        }
      }
    ],
    "next_token": null
  }
}
Enter fullscreen mode Exit fullscreen mode

Gỡ lỗi nhanh: Dùng Apidog để kiểm tra thứ tự header, timestamp, và trạng thái token OAuth.

Tích hợp CI/CD:

name: SP-API Integration Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Apidog Tests
        uses: apidog/test-action@v1
        with:
          project-id: ${{ secrets.APIDOG_PROJECT_ID }}
          api-key: ${{ secrets.APIDOG_API_KEY }}
          environment: sandbox
      - name: Notify on Failure
        if: failure()
        run: |
          echo "Kiểm thử SP-API thất bại - kiểm tra bảng điều khiển Apidog"
Enter fullscreen mode Exit fullscreen mode

Tham chiếu ID Thị trường

Quốc gia ID Thị trường
Hoa Kỳ ATVPDKIKX0DER
Canada A2EUQ1WTGCTBG2
Mexico A1AM78C64UM0Y8
Vương quốc Anh A1F83G8C2ARO7P
Đức A1PA6795UKMFR9
Pháp A13V1IB3VIYZZH
Ý APJ6JRA9NG5V4
Tây Ban Nha A1RKKUPIHCS9HS
Nhật Bản A1VC38T7YXB528
Úc A39IBJ37TRP1C6
Ấn Độ A21TJRUUN4KGV
Brazil A2Q3Y263D00KWC

Khắc phục sự cố phổ biến

403 Không được ủy quyền

  • Kiểm tra credential AWS, ARN IAM, token OAuth chưa hết hạn, chữ ký SigV4, header x-amz-access-token.

429 Vượt quá giới hạn tốc độ

  • Thêm queue, backoff, phân trang (next_token), giám sát header rate limit, yêu cầu tăng quota nếu cần.

404 Không tìm thấy

  • Kiểm tra đúng endpoint theo khu vực, ID thị trường hợp lệ, tài nguyên tồn tại, version API chuẩn.

400 Yêu cầu không hợp lệ

  • Định dạng ngày ISO 8601, đủ tham số bắt buộc, ID thị trường chuẩn, JSON đúng format.
const validateIsoDate = (dateString) => {
  const date = new Date(dateString);
  if (isNaN(date.getTime())) throw new Error('Định dạng ngày ISO 8601 không hợp lệ');
  return dateString;
};
Enter fullscreen mode Exit fullscreen mode

Báo cáo INIT_STATE

  • Chờ lâu hơn, thử phạm vi nhỏ hơn, kiểm tra loại báo cáo và quyền, polling mỗi 30s.

Không nhận được thông báo SNS

  • Kiểm tra trạng thái subscription, policy SNS, endpoint public, log CloudWatch, xác thực SSL, trả về 200 OK trong 30s, xác nhận SubscriptionConfirmation.

Checklist trước triển khai sản xuất

  • [ ] Đăng ký app ở Seller Central production
  • [ ] Cấu hình IAM role sản xuất, quyền tối thiểu
  • [ ] Cập nhật redirect URI production
  • [ ] Lưu trữ token an toàn (mã hóa DB)
  • [ ] Logic refresh token tự động
  • [ ] Thêm queue/giới hạn tốc độ
  • [ ] Đặt đích SNS thông báo
  • [ ] Xử lý lỗi toàn diện
  • [ ] Log mọi request với request ID
  • [ ] Giám sát limit sử dụng
  • [ ] Hướng dẫn quy trình cho nhóm
  • [ ] Kiểm thử nhiều marketplace ID
  • [ ] Tài liệu hóa luồng OAuth cho seller
  • [ ] Retry logic exponential backoff
  • [ ] Alert lỗi xác thực

Monitoring & Alert

const metrics = {
  apiCalls: { total: 0, successful: 0, failed: 0, rateLimited: 0 },
  rateLimitUsage: {
    orders: { current: 0, limit: 10 },
    inventory: { current: 0, limit: 2 },
    listings: { current: 0, limit: 10 }
  },
  oauthTokens: { active: 0, expiring_soon: 0, refresh_failures: 0 },
  notifications: { received: 0, processed: 0, failed: 0 },
  reports: { pending: 0, completed: 0, failed: 0 }
};
const failureRate = metrics.apiCalls.failed / metrics.apiCalls.total;
if (failureRate > 0.05) sendAlert('Tỷ lệ thất bại SP-API trên 5%');
for (const [endpoint, usage] of Object.entries(metrics.rateLimitUsage)) {
  if (usage.current / usage.limit > 0.8) {
    sendAlert(`${endpoint} giới hạn tốc độ đạt 80% công suất`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Use-case thực tế

Đồng bộ tồn kho đa thị trường

  • Webhook SNS nhận sự kiện InventoryLevels
  • Hệ thống tính toán số lượng, gọi API cập nhật có queue giới hạn tốc độ
  • Không còn oversell, tiết kiệm 25h/tuần

Tự động thực hiện đơn hàng

  • Webhook OrderStatusChange tự động gửi đơn sang WMS, cập nhật tracking trả về SP-API
  • Xử lý >200 đơn/ngày tự động, khách nhận thông báo vận chuyển trong 2 phút

Dashboard phân tích seller

  • Thu thập dữ liệu đa seller qua OAuth, quản lý token
  • Tổng hợp báo cáo đơn hàng, tồn kho, pricing, advertising real-time cho >500 tài khoản

Kết luận

Amazon SP-API cung cấp quyền truy cập toàn diện cho hệ thống người bán—cần quản lý xác thực, ký SigV4, logic retry, giới hạn tốc độ và kiểm thử kỹ lưỡng để vận hành ổn định ở production.

Apidog giúp kiểm thử, mô phỏng và tài liệu hóa workflow SP-API hiệu quả cho nhóm dev.


Phần Câu hỏi thường gặp

Amazon SP-API là gì?

Amazon Selling Partner API (SP-API) là API REST truy cập dữ liệu trung tâm người bán như đơn hàng, tồn kho, danh sách sản phẩm, báo cáo. Thay thế MWS với bảo mật OAuth 2.0 và ký AWS SigV4.

Làm cách nào để có thông tin xác thực SP-API?

Đăng ký ứng dụng tại Seller Central (Apps and Services > Develop Apps), nhận Client ID, Client Secret, Application ID. Tạo IAM Role trên AWS và liên kết với ứng dụng.

SP-API có miễn phí không?

Miễn phí cho seller Amazon đăng ký. Giới hạn tốc độ theo endpoint, muốn nâng quota phải được Amazon duyệt.

SP-API xác thực như thế nào?

OAuth 2.0 + IAM Role. Mọi request cần ký AWS Signature V4 với credential tạm thời lấy qua OAuth.

Làm sao xử lý rate limit SP-API?

Queue request theo limit từng endpoint, theo dõi x-amzn-RateLimit-Limit, exponential backoff khi 429.

Có kiểm thử SP-API mà không cần seller account thật không?

Có. Amazon cung cấp môi trường sandbox cho phát triển, nhiều endpoint có thể test mà không ảnh hưởng dữ liệu thật.

Webhook hoạt động thế nào với SP-API?

Sử dụng Amazon SNS, đăng ký event, cấu hình SNS topic, xây dựng endpoint HTTPS nhận notification, xác nhận subscription.

Token OAuth hết hạn thì thế nào?

Token hết hạn sau 1h, dùng refresh token lấy access token mới. Nên tự động refresh trước khi hết hạn.

Di chuyển từ MWS sang SP-API?

Cập nhật từ MWS token sang OAuth 2.0, ký SigV4, update endpoint và chuyển đổi parsing XML sang JSON.

Tại sao lỗi 403 Unauthorized?

Có thể hết hạn token, IAM Role sai, ký SigV4 sai, thiếu header access token, role chưa liên kết app.


Tài liệu chi tiết hơn, các ví dụ thực tế và hướng dẫn CI/CD hãy dùng thử Apidog cho project SP-API của bạn.

Top comments (0)