Idempotency
Lỗi mạng hay hết thời gian chờ khiến bạn không biết request đã tới nơi hay chưa. Nếu cứ gửi lại, khách có thể nhận cùng một email hai lần hoặc bị yêu cầu trả tiền hai lần. Idempotency key giúp việc gửi lại an toàn: Hạ Tầng chỉ làm việc đó một lần và trả cùng một kết quả cho mọi lần lặp lại.
Hỗ trợ ở đâu
Gửi key trong header Idempotency-Key. Key là chuỗi bất kỳ do bạn chọn, tối đa 256 ký tự.
| Endpoint | Phạm vi của key | Lặp lại với cùng body | Lặp lại với body khác |
|---|---|---|---|
POST /emails | Doanh nghiệp | 200 với cùng id; không gửi lại thư | 400 invalid_idempotent_request |
POST /payments | Doanh nghiệp và môi trường Thanh toán | 201 với cùng thanh toán | 400 invalid_idempotent_request |
POST /chat/conversations/{id}/messages | Hội thoại | 200 với cùng tin nhắn và "duplicate": true | 200 với tin nhắn đầu (body không được so sánh) |
POST /emails/batch và các endpoint khác không nhận idempotency key.
Gửi idempotency key
Dùng key xác định nghiệp vụ, không phải lần gọi HTTP: mã đơn hàng, welcome-<user id>, invoice-<số>-reminder-1. Tạo key một lần, lưu cùng tác vụ, và dùng lại ở mọi lần thử.
- cURL
- Node.js
- Python
- PHP
curl -X POST https://api.hatang.dev/emails \
-H "Authorization: Bearer $HATANG_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001-confirmation" \
-d '{
"from": "Shop <[email protected]>",
"to": "[email protected]",
"subject": "Đơn #1001 đã được xác nhận",
"html": "<p>Cảm ơn bạn đã đặt hàng!</p>"
}'
import { Hatang } from "@hatang/sdk";
const hatang = new Hatang(process.env.HATANG_API_KEY, { baseUrl: "https://api.hatang.dev" });
// Email
await hatang.emails.send(
{ from: "Shop <[email protected]>", to: "[email protected]", subject: "Đơn #1001 đã được xác nhận", html: "<p>Cảm ơn!</p>" },
{ idempotencyKey: "order-1001-confirmation" },
);
// Thanh toán
await hatang.payments.create({ amount: 150000, description: "Đơn 1001" }, { idempotencyKey: "order-1001" });
// Trả lời chat
await hatang.chat.conversations.send("cnv_123...", { content: "Đơn của anh được giao trong hôm nay ạ." }, { idempotencyKey: "order-1001-shipped" });
from hatang import Hatang
hatang = Hatang() # đọc HATANG_API_KEY
hatang.emails.send(
{
"subject": "Đơn #1001 đã được xác nhận",
"html": "<p>Cảm ơn bạn đã đặt hàng!</p>",
},
idempotency_key="order-1001-confirmation",
)
$res = $client->post('/emails', [
'headers' => ['Idempotency-Key' => 'order-1001-confirmation'],
'json' => [
'subject' => 'Đơn #1001 đã được xác nhận',
'html' => '<p>Cảm ơn bạn đã đặt hàng!</p>',
],
]);
Key được lưu thế nào
- Email và thanh toán. Key được lưu cùng email hoặc thanh toán mà nó tạo ra, kèm bản băm của body request. Lần lặp lại với cùng key được so với bản băm đó. Key không hết hạn: còn email hay thanh toán thì key vẫn trỏ tới nó. Email được trả lại theo key không bị tính thêm vào hạn mức trong ngày.
- Tin nhắn chat. Key trở thành
client_idcủa tin nhắn. Lần lặp lại trong cùng hội thoại trả về tin nhắn đã có với"duplicate": true. Widget và SDK di động cũng làm như vậy vớiclient_idgửi kèm mỗi tin nhắn, nhờ đó tin nhắn gửi hai lần khi mạng chập chờn chỉ hiện một lần trong hộp thư. - Môi trường Thanh toán. Key của thanh toán tính riêng theo môi trường, nên cùng một mã đơn dùng được một lần ở development và một lần ở production.
Xung đột
name | Status | Nguyên nhân | Nên làm gì |
|---|---|---|---|
invalid_idempotent_request | 400 | Key đã được dùng với body khác | Bạn đã dùng lại key cho một nghiệp vụ khác. Tạo key mới; đừng bao giờ đổi body khi thử lại. |
concurrent_idempotent_requests | 409 | Chỉ ở Email: hai request cùng key tới cùng một lúc | Chờ một chút rồi thử lại với cùng key; bạn nhận kết quả của request đầu. |
{
"statusCode": 400,
"name": "invalid_idempotent_request",
"message": "Same idempotency key used with a different request payload"
}
"Body khác" là bất kỳ giá trị nào thay đổi, như một mốc thời gian mới hay một id ngẫu nhiên được sinh lại trong payload. Hãy dựng payload một lần, lưu cùng key, và gửi lại đúng payload đó ở mọi lần thử.
Lời khuyên khi thử lại
- Tạo key trước lần gọi đầu tiên và lưu cùng tác vụ, để tiến trình có chết giữa chừng thì key vẫn còn.
- Chỉ thử lại lỗi tạm thời: lỗi mạng,
429 rate_limit_exceededvà5xx. Tăng thời gian chờ theo cấp số nhân và tôn trọngretry-after(Giới hạn tần suất). - Không thử lại lỗi
4xxngoài429và409 concurrent_idempotent_requests; hãy sửa request. - Coi lần trả lại là thành công.
200cho email lặp lại hay tin nhắn chat có"duplicate": truenghĩa là lần đầu đã thành công. - Làm cho handler webhook cũng idempotent. Lượt gửi webhook có thể lặp lại; xem Webhook.