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.
💡 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)
- Mọi request cần:
- Chữ ký SigV4
- Access token OAuth
- Quyền IAM phù hợp
- 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
- Vào Amazon Developer Central
- Đăng nhập bằng tài khoản có quyền Seller Central
- Vào Selling Partner API và chấp nhận thỏa thuận
Bước 2: Đăng ký ứng dụng
- Đăng nhập Seller Central
- Vào Apps and Services > Develop Apps
- 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)
- 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"
Bước 3: Tạo vai trò IAM cho SP-API
- Đăng nhập AWS IAM
- Vào Roles > Create Role
- 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 Mỹ:
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/*"
]
}
]
}
Ghi lại ARN role vừa tạo.
Bước 5: Liên kết IAM Role với ứng dụng
- Vào Seller Central > Develop Apps
- Chọn app > Edit > IAM Role ARN
- 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:
- Người bán nhấn Authorize trên ứng dụng của bạn
- Chuyển hướng đến URL OAuth Amazon
- Đăng nhập, cấp quyền
- Amazon redirect về với authorization code
- Đổi code lấy LWA token
- Đổi LWA token lấy SP-API access token
- Gọi API (phải ký SigV4)
- 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}`);
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');
}
});
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;
};
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;
};
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);
}
}
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();
};
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);
};
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);
};
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);
};
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);
};
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);
};
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);
};
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);
};
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);
};
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);
};
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;
};
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);
};
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 });
};
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');
});
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;
}
}
}
};
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));
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;
}
}
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
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
};
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));
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
}
}
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"
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;
};
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`);
}
}
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
OrderStatusChangetự độ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)