API v1 · JSON · HTTPS

Tài liệu API eOTP

Gửi mã OTP cho người dùng của bạn qua Zalo hoặc SMS brandname. Máy chủ của bạn gọi otp/send để gửi mã, otp/verify để kiểm mã người dùng nhập, và otp/status khi cần biết tin đã tới đâu. Mọi lời gọi gửi kèm API key và Secret key của dự án.

Bắt đầu nhanh

  1. Tạo tài khoản và dự án

    Đăng ký tại trang đăng ký (xác minh số điện thoại bằng một mã OTP), rồi nộp dự án: chọn loại (Android, iOS hoặc website), điền tên, đường dẫn cửa hàng hoặc tên miền và mô tả ngắn cách bạn dùng OTP. Hệ thống tự rà soát, thường trong vòng 2 phút; dự án đạt ở trạng thái approved, gói FREE (100 OTP/tháng miễn phí).

  2. Xác minh để lên DEVELOPER

    Website dùng Popup xác thực (cách mặc định) hoặc nút hiện sẵn (tuỳ chọn); app gọi SDK kiểm giấy phép (xem Kích hoạt DEVELOPER). Đủ bằng chứng, dự án tự lên DEVELOPER (100,000 OTP/tháng). Bước này không bắt buộc để thử API.

  3. Lấy API key và Secret key

    Trong trang dự án, mở tab Tích hợp. API key (eo_live_…) luôn hiện; Secret key (eo_sec_…) chỉ hiện một lần khi tạo. Lưu cả 2 vào biến môi trường trên máy chủ: EASYOTP_API_KEY, EASYOTP_API_SECRET.

Thử ngay từ dòng lệnh: gửi mã, rồi kiểm mã bạn nhận được trên điện thoại.

curl
# Gửi mã tới số Việt Nam
curl -X POST https://eotp.com.vn/api/v1/otp/send \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"phone":"0912345678","country_code":"84"}'
# → {"ok":true,"request_id":"3f9a1c0e8b7d4a26b5e0c9d18f2a7b64", ...}

# Kiểm mã người dùng nhập
curl -X POST https://eotp.com.vn/api/v1/otp/verify \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"request_id":"3f9a1c0e8b7d4a26b5e0c9d18f2a7b64","code":"123456"}'
# → {"ok":true,"verified":true}

Xác thực

Mỗi yêu cầu tới otp/send, otp/status, otp/verify và project gửi kèm 2 khoá của dự án:

  • API key, dạng eo_live_ + 16 ký tự: định danh dự án, luôn xem lại được ở tab Tích hợp. Gửi trong header X-Api-Key.
  • Secret key, dạng eo_sec_ + 32 ký tự: chỉ hiện một lần khi tạo, eOTP chỉ lưu bản băm. Gửi trong header X-Api-Secret.
Header
X-Api-Key: eo_live_xxxxxxxxxxxxxxxx
X-Api-Secret: eo_sec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Hoặc gộp vào một header: Authorization: Bearer <api_key>:<secret>.

Tình huốngHTTPerror
Thiếu hoặc sai API key401invalid_api_key
API key đúng nhưng thiếu, sai Secret key hoặc secret đã bị tạo lại401invalid_api_secret
Khoá cũ dạng một khoá đã quá 30 ngày chuyển đổi401legacy_auth_expired
Một địa chỉ IP xác thực sai nhiều lần liên tiếp429auth_rate_limited
Dự án chưa duyệt, bị từ chối, tạm ngưng hoặc tài khoản bị khoá403project_not_active

Chặn theo IP khi xác thực sai nhiều. Mỗi phản hồi 401 tính vào bộ đếm của IP gọi (IPv6 gộp theo dải /64). Sai nhiều lần liên tiếp thì mọi yêu cầu từ IP đó bị trả 429 auth_rate_limited trong một thời gian, kể cả khi khoá đúng (chờ theo retry_in, header Retry-After). Khi triển khai khoá mới, kiểm biến môi trường trước để máy chủ không tự khoá mình.

Chuyển đổi từ khoá cũ. Khoá cũ dạng một khoá (eo_live_ + 32 ký tự gửi một mình) vẫn dùng được tới 30 ngày sau khi bạn tạo secret, kèm header Deprecation: true và X-Eotp-Warning ghi ngày hết hạn. Quá hạn trả 401 legacy_auth_expired. Vào trang dự án, bấm Tạo secret mới để nhận cặp API key + Secret key rồi cập nhật máy chủ.

Chỉ gọi API từ máy chủ của bạn. Không đưa Secret key vào app Android/iOS, JavaScript trên trình duyệt hay kho mã công khai: ai có đủ 2 khoá đều gửi được OTP và tiêu hạn mức, số dư của bạn. API không mở CORS nên trình duyệt chặn lời gọi trực tiếp. API key một mình là định danh công khai, nhúng vào iframe hay SDK kiểm giấy phép đều an toàn.

Lộ Secret key? Vào tab Tích hợp và tạo secret mới. Secret cũ mất hiệu lực ngay lập tức. API key cũng tạo lại được ở cùng chỗ (nút Tạo API key mới): khoá cũ bị từ chối ngay, và vì đường dẫn /embed/<api_key> đi theo khoá nên bạn cập nhật header X-Api-Key, mã iframe nếu dùng nút hiện sẵn và SDK kiểm giấy phép trong app; Popup xác thực không cần sửa trang. Secret key giữ nguyên.

Quy ước chung

  • URL gốc: https://eotp.com.vn/api/v1. Chỉ nhận HTTPS.
  • Body gửi lên là JSON (Content-Type: application/json), UTF-8. Phản hồi luôn là JSON.
  • Mọi phản hồi có trường ok. Thành công: "ok": true, HTTP 200. Lỗi: "ok": false, mã lỗi máy đọc trong error và lời nhắn trong message, kèm trường phụ tuỳ lỗi.
  • Xử lý theo error, không dựa vào message vì lời nhắn có thể thay đổi.
  • Số tiền là số nguyên ở đơn vị nhỏ nhất của ví: VND tính bằng đồng, USD tính bằng cent. Trường currency cho biết loại tiền.
  • Mốc thời gian là Unix (giây, UTC). Tháng và ngày của hạn mức tính theo giờ UTC.
  • Header X-Request-Id: nếu bạn gửi (tối đa 64 ký tự chữ, số, . _ : -), hệ thống trả lại đúng giá trị đó; nếu không, hệ thống tự tạo. Ghi giá trị này vào log để đối chiếu khi cần hỗ trợ.
Dạng phản hồi lỗi
{
  "ok": false,
  "error": "invalid_phone",
  "message": "Số điện thoại chưa đúng.",
  "reason": "too_short"
}

Gửi mã OTP

POSThttps://eotp.com.vn/api/v1/otp/send

Tạo một mã OTP mới và gửi tới số điện thoại. Hệ thống tự sinh mã; bạn không cần và không bao giờ thấy mã.

Tham số (body JSON)

TrườngKiểuMô tả
phonebắt buộcstringSố dạng E.164 (+84912345678) hoặc số nội địa (0912345678) kèm country_code. Được phép có dấu cách, chấm, gạch.
country_codetuỳ chọnstringMã nước không dấu +, ví dụ 84. Bỏ trống thì mặc định 84 (Việt Nam). Khi phone đã có dạng +… thì dùng mã trong số; country_code mâu thuẫn với mã đó trả invalid_phone.
channeltuỳ chọnstringzalo, sms, both hoặc zalo_then_sms (xem Kênh). Bỏ trống thì dùng kênh mặc định trong Cài đặt gửi của dự án.
callback_urltuỳ chọnstringURL trên máy chủ của bạn để eOTP POST kết quả gửi khi yêu cầu chốt sent hoặc failed (xem Callback). Phải là https://, cổng 443, tên miền phân giải ra IP công khai, tối đa 500 ký tự. Sai trả 400 invalid_callback_url.

Thời gian sống, độ dài và kiểu mã đặt theo dự án, không truyền trong body: vào trang dự án ▸ Cài đặt gửi. Thời gian sống 60–900 giây (1–15 phút, mặc định 300); độ dài 4–10 ký tự (mặc định 6); kiểu mã digits (chỉ số 0–9) hoặc alnum (chữ + số, bảng A–Z bỏ O, I và số 2–9, dễ đọc trên điện thoại).

Yêu cầu
curl -X POST https://eotp.com.vn/api/v1/otp/send \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+84912345678","channel":"zalo_then_sms","callback_url":"https://api.shop.vn/eotp/callback"}'

Phản hồi thành công (200)

Có 2 dạng, tuỳ chế độ vận hành của hệ thống. Code của bạn nên xử lý được cả hai: chỉ cần ok và request_id là đủ để đi tiếp tới verify.

  • Hàng đợi ("status": "queued"): eOTP nhận yêu cầu, giữ tiền và trả lời ngay; bộ gửi nền chuyển tin trong khoảng eta_ms mili giây. Mã đã được tạo nên người dùng có thể nhập và bạn gọi verify bình thường. Muốn biết tin đã tới đâu, gọi otp/status.
  • Đồng bộ (không có status): eOTP gửi xong mới trả lời, kèm sent_channels.
TrườngKiểuMô tả
request_idstringMã yêu cầu, 32 ký tự hex. Lưu lại để gọi verify và status.
statusstringChỉ ở chế độ hàng đợi: queued.
eta_msnumberChỉ ở chế độ hàng đợi: ước tính số mili giây tới khi tin được gửi đi. Tăng khi hệ thống đông, yêu cầu không bị từ chối vì đông.
channelstringChế độ thực dùng. Có thể khác chế độ bạn chọn: gói FREE chỉ gửi một kênh nên both/zalo_then_sms chuyển về kênh mặc định của dự án (zalo hoặc sms, chọn ở Cài đặt gửi); nước chưa có Zalo thì chuyển về sms; ví chỉ đủ Zalo thì zalo_then_sms hạ về zalo.
channel_notestringChỉ có khi kênh bị đổi theo nước người nhận: zalo_unavailable_in_country hoặc sms_unavailable_in_country.
sent_channelsstring[]Chế độ đồng bộ: kênh đã gửi thành công, ["zalo"], ["sms"] hoặc ["zalo","sms"].
expires_innumberSố giây mã còn hiệu lực (theo cài đặt dự án).
code_typestringdigits hoặc alnum: kiểu mã người dùng sẽ nhận, dùng để chọn bàn phím số hay chữ cho ô nhập.
cost, currencynumber, stringSố tiền ví đang trừ cho lần gửi này, ở đơn vị nhỏ nhất của currency (VND hoặc USD). Bằng 0 ở gói FREE. Với zalo_then_sms gồm cả giá SMS dự phòng; phần này được hoàn khi Zalo tới máy.
balancenumberSố dư ví sau khi trừ (khi có), cùng đơn vị với cost.
cost_vnd, balance_vndnumberGiữ cho tương thích khi ví VND. Ví USD dùng cost_usd_cents, balance_usd_cents.
phone_e164stringSố đã chuẩn hoá dạng E.164, ví dụ +84912345678. Nên dùng làm khoá số điện thoại của người dùng.
200 OK · hàng đợi
{
  "ok": true,
  "request_id": "3f9a1c0e8b7d4a26b5e0c9d18f2a7b64",
  "status": "queued",
  "eta_ms": 400,
  "expires_in": 300,
  "cost": 0,
  "currency": "VND",
  "code_type": "digits",
  "phone_e164": "+84912345678"
}
200 OK · đồng bộ
{
  "ok": true,
  "request_id": "3f9a1c0e8b7d4a26b5e0c9d18f2a7b64",
  "channel": "zalo_then_sms",
  "sent_channels": ["zalo"],
  "expires_in": 300,
  "cost_vnd": 0,
  "currency": "VND",
  "cost": 0,
  "phone_e164": "+84912345678"
}

Lỗi riêng của endpoint này

errorHTTPKhi nàoTrường thêm
invalid_phone400Thiếu số, sai định dạng, quá ngắn/quá dài, hoặc country_code mâu thuẫn với mã trong số.reason: empty, invalid, too_short, too_long
country_not_supported400Mã nước chưa được mở gửi OTP. Danh sách nước ở GET /pricing.country_code
channel_not_supported_in_country400Nước của số nhận chưa có kênh nào, hoặc kênh dùng được cho nước đó đang tạm dừng.country_code, country_iso, channels_enabled?
invalid_channel400Kênh không thuộc 4 chế độ hoặc đang tạm dừng.channels_enabled
invalid_callback_url400callback_url không hợp lệ. Không trừ tiền, không gửi tin.reason: bad_url, not_https, bad_port, private_host
quota_exceeded429Tài khoản dùng hết hạn mức tháng. Hạn mức tính theo tài khoản: mọi dự án của bạn dùng chung một bể.month_used, month_quota
daily_quota_exceeded429Tài khoản dùng hết hạn mức ngày (FREE: tối đa 20 OTP/ngày; DEVELOPER: tối đa 10,000 OTP/ngày). Có header Retry-After.daily_quota, today_used, resets_at, retry_after
insufficient_balance402Gói trả phí: số dư ví không đủ cho tin này.currency, balance, cost (+ balance_vnd, cost_vnd hoặc balance_usd_cents, cost_usd_cents)
unavailable503Dịch vụ gửi tạm thời chưa sẵn sàng.
send_failed502Chế độ đồng bộ: nhà mạng/Zalo không nhận tin. Không trừ tiền.

Không tự gọi lại send khi chưa nhận phản hồi: người dùng có thể nhận 2 mã. Đặt timeout từ 35 giây trở lên, vì ở chế độ đồng bộ khi Zalo không nhận, hệ thống gửi SMS dự phòng ngay trong cùng lời gọi.

Chế độ thử: dự án đã duyệt bật Chế độ thử ở tab API key & tích hợp (hoặc ở khối Nâng cấp lên DEVELOPER). Khi bật, gọi send tới số thử của eOTP (hiện ngay cạnh công tắc, mặc định +84 901 888 484) thì không gửi tin thật, không trừ ví và không tính vào hạn mức; phản hồi có "test_mode": true và "esms_code": "test". Gọi verify với mã 000000 là thành công. Các số khác vẫn gửi thật và trừ ví như thường.

Trạng thái gửi

GEThttps://eotp.com.vn/api/v1/otp/status?request_id=…

Tra trạng thái gửi của một yêu cầu do chính dự án của khoá tạo. Dùng khi send trả "status": "queued" và bạn muốn báo người dùng "mã đã gửi" hoặc gợi ý đổi kênh khi gửi lỗi. Không cần đợi sent mới gọi verify.

Yêu cầu
curl "https://eotp.com.vn/api/v1/otp/status?request_id=3f9a1c0e8b7d4a26b5e0c9d18f2a7b64" \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET"
statusÝ nghĩa
queuedĐã nhận, đang chờ bộ gửi nền.
sendingBộ gửi đang chuyển tin cho nhà mạng/Zalo.
sentNhà mạng/Zalo đã nhận tin. sent_at là lúc gửi.
failedGửi không được; error cho biết lý do. Tiền đã giữ được hoàn lại.
expiredMã đã hết hạn.
200 OK
{
  "ok": true,
  "request_id": "3f9a1c0e8b7d4a26b5e0c9d18f2a7b64",
  "status": "sent",
  "channel": "sms",
  "sent_at": 1794268800,
  "error": null,
  "attempts": 1
}

attempts là số lần bộ gửi đã thử. request_id sai dạng, không tồn tại hoặc của dự án khác trả 404 not_found. Nên tra cách nhau ít nhất 1 giây, dừng khi gặp sent, failed hoặc expired.

Kiểm mã OTP

POSThttps://eotp.com.vn/api/v1/otp/verify

Kiểm mã người dùng nhập với request_id nhận từ send. Mỗi mã chỉ dùng được một lần. Chỉ kiểm được yêu cầu do chính dự án của khoá tạo; request_id của dự án khác trả not_found.

Tham số (body JSON)

TrườngKiểuMô tả
request_idbắt buộcstringGiá trị request_id trả về từ send.
codebắt buộcstringMã người dùng nhập. Kiểu alnum so không phân biệt hoa/thường (ab7k = AB7K).
phonetuỳ chọnstringSố đã gửi mã (E.164 hoặc nội địa kèm country_code), chuẩn hoá như send. Gửi kèm mà khác số của yêu cầu thì trả not_found.
country_codetuỳ chọnstringDùng cùng phone dạng nội địa, mặc định 84.
Yêu cầu
curl -X POST https://eotp.com.vn/api/v1/otp/verify \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"request_id":"3f9a1c0e8b7d4a26b5e0c9d18f2a7b64","code":"123456"}'
200 OK
{
  "ok": true,
  "verified": true
}

Lỗi riêng của endpoint này

errorHTTPKhi nàoTrường thêm
wrong_code400Mã sai (hoặc thiếu code). Người dùng được nhập lại.attempts_left
expired410Mã đã hết hạn hoặc đã được dùng.
too_many_attempts429Nhập sai 5 lần, mã bị huỷ. Cần gửi mã mới.
not_found404request_id không tồn tại, thuộc dự án khác, hoặc phone không khớp.
rate_limited429Quá nhiều yêu cầu chưa xác thực (thiếu hoặc sai khoá); thử lại sau. Gọi bằng khoá + secret đúng không gặp lỗi này. Có header Retry-After.retry_in
400 Mã sai
{
  "ok": false,
  "error": "wrong_code",
  "message": "Mã chưa đúng. Bạn còn 4 lần thử.",
  "attempts_left": 4
}

Callback về máy chủ của bạn

Truyền callback_url khi gọi otp/send để eOTP tự báo kết quả gửi, thay vì tra otp/status nhiều lần. Khi yêu cầu chốt sent hoặc failed, eOTP gửi một lời gọi POST tới URL đó. Không truyền callback_url thì không có callback; tra otp/status vẫn dùng được như cũ.

Hoàn tiền, huỷ SMS dự phòng khi Zalo tới máy và báo cáo nhà mạng vẫn do eOTP tự xử lý; callback chỉ để bạn biết kết quả.

Yêu cầu eOTP gửi tới bạn

HeaderGiá trị
Content-Typeapplication/json
User-AgentBắt đầu bằng eOTP-Callback/1.0.
X-Eotp-Request-IdBằng request_id trong thân, dùng để bỏ qua lời gọi trùng.
X-Eotp-TimestampMốc Unix (giây, UTC) lúc eOTP gửi lời gọi này. Mỗi lần gửi lại có mốc mới.
X-Eotp-Signaturesha256= + HMAC-SHA256 (hex chữ thường) của chuỗi <X-Eotp-Timestamp>.<thân thô>, khoá là Callback secret của dự án.
TrườngKiểuMô tả
request_idstringMã yêu cầu trả về từ send.
statusstringsent (nhà mạng/Zalo đã nhận tin) hoặc failed (tiền đã giữ được hoàn lại).
channelstringKênh đã dùng, như ở otp/status.
sent_atnumber | nullMốc Unix (giây, UTC) lúc gửi; null khi failed.
errorstring | nullLý do khi failed.
phone_e164stringSố nhận dạng E.164.
POST tới callback_url
POST /eotp/callback HTTP/1.1
Host: api.shop.vn
Content-Type: application/json
User-Agent: eOTP-Callback/1.0 (+https://eotp.com.vn)
X-Eotp-Request-Id: 3f9a1c0e8b7d4a26b5e0c9d18f2a7b64
X-Eotp-Timestamp: 1794268800
X-Eotp-Signature: sha256=097235b3686b78ffc336fcb92452539ce8b3f434c063044289e416fbff8c0cc7

{"request_id":"3f9a1c0e8b7d4a26b5e0c9d18f2a7b64","status":"sent","channel":"sms","sent_at":1794268800,"error":null,"phone_e164":"+84912345678"}

Xác minh chữ ký

Lấy Callback secret ở trang dự án ▸ tab Tích hợp. Ghép giá trị header X-Eotp-Timestamp, một dấu chấm và thân thô đúng từng byte (đọc trước khi parse JSON) thành timestamp + "." + body, tính HMAC-SHA256 bằng Callback secret, rồi so sha256= + kết quả với header X-Eotp-Signature bằng hàm so sánh hằng thời gian. Không khớp thì trả 401 và bỏ qua.

Chống phát lại: từ chối lời gọi khi X-Eotp-Timestamp thiếu, không phải số, hoặc lệch giờ máy chủ của bạn quá 300 giây (|now − timestamp| > 300). Đồng bộ giờ máy chủ bằng NTP để không từ chối nhầm.

Node.js (Express)
const crypto = require('crypto');
app.post('/eotp/callback', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-Eotp-Timestamp') || '';
  if (!/^\d+$/.test(ts) || Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > 300) return res.sendStatus(401);
  const sig = 'sha256=' + crypto.createHmac('sha256', process.env.EOTP_CALLBACK_SECRET).update(ts + '.').update(req.body).digest('hex');
  const got = Buffer.from(req.get('X-Eotp-Signature') || '');
  if (got.length !== sig.length || !crypto.timingSafeEqual(got, Buffer.from(sig))) return res.sendStatus(401);
  const data = JSON.parse(req.body); /* data.request_id, data.status */ res.sendStatus(200);
});

Phản hồi và gửi lại

Trả bất kỳ mã 2xx trong 5 giây là eOTP coi như bạn đã nhận. Mã khác, quá 5 giây hoặc không kết nối được thì eOTP gửi lại theo lịch dưới. eOTP không theo chuyển hướng (3xx tính là lỗi).

Lần gọiThời điểmTimeout
1Ngay khi yêu cầu chốt sent/failed5 giây
230 giây sau lần 1 lỗi5 giây
35 phút sau lần 2 lỗi5 giây

Hết 3 lần mà chưa nhận 2xx thì dừng gửi. Kết quả vẫn tra được bằng GET /otp/status. Cùng một request_id có thể tới hơn một lần (mạng chập chờn); xử lý theo X-Eotp-Request-Id để không ghi trùng.

Giữ Callback secret ở máy chủ, như Secret key. Lộ secret thì người khác giả được callback; liên hệ hỗ trợ eOTP để cấp secret mới.

Thông tin dự án

GEThttps://eotp.com.vn/api/v1/project

Xem trạng thái, gói, lượng OTP đã dùng và số dư ví của tài khoản sở hữu dự án. Hữu ích để theo dõi hạn mức hoặc kiểm khoá khi triển khai.

Yêu cầu
curl https://eotp.com.vn/api/v1/project \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET"
Trường trong projectKiểuMô tả
id, namenumber, stringMã và tên dự án.
typestringandroid, ios hoặc web.
statusstringLuôn là approved khi gọi được (trạng thái khác trả 403).
tiernumberGói: 1 FREE, 2 DEVELOPER, 3 UNLIMITED, 4 BUSINESS.
default_channelstringKênh dùng khi send không truyền channel.
otp_ttlnumberThời gian sống của mã (giây).
month_used, month_quotanumber, number | nullĐã dùng / hạn mức tháng (UTC); null là không giới hạn.
today_usednumberSố OTP đã gửi hôm nay (UTC).
currencystringVí đang dùng: VND hoặc USD.
balance_minornumberTổng số dư = ví gốc + ví tặng, đơn vị nhỏ nhất (đồng hoặc cent).
balance_main_minor, balance_bonus_minornumberSố dư ví gốc và ví tặng.
balance_vnd, balance_main_vnd, balance_bonus_vndnumberChỉ khi ví VND (giữ cho tương thích). Ví USD: balance_usd_cents, balance_main_usd_cents, balance_bonus_usd_cents.
200 OK
{
  "ok": true,
  "project": {
    "id": 12,
    "name": "Shop Hoa Mai",
    "type": "web",
    "status": "approved",
    "tier": 2,
    "default_channel": "zalo_then_sms",
    "otp_ttl": 300,
    "month_used": 37,
    "month_quota": 10000,
    "today_used": 4,
    "currency": "VND",
    "balance_minor": 120000,
    "balance_main_minor": 100000,
    "balance_bonus_minor": 20000,
    "balance_vnd": 120000,
    "balance_main_vnd": 100000,
    "balance_bonus_vnd": 20000
  }
}

Kích hoạt DEVELOPER

Website có hai cách tích hợp: Popup xác thực (mặc định) và Nút hiện sẵn. Nút hiện sẵn đặt nút "Xác thực eOTP" lên trang của bạn; người dùng bấm nút là popup xác thực mở, phiên vẫn do máy chủ của bạn tạo như kiểu popup (xem Bấm nút mở popup). Chọn cách ở trang dự án ▸ tab API key & tích hợp.

Dự án mới được duyệt ở gói FREE (Cấp 1). Để lên DEVELOPER (Cấp 2) không cần chờ duyệt tay: bạn chứng minh dự án đã tích hợp eOTP thật, hệ thống tự mở cấp.

  • Website, popup xác thực: trong 30 ngày gần nhất có ít nhất một lần người dùng xác minh thành công trong popup mở từ đúng tên miền của dự án (mã gửi thật, không tính chế độ thử), và trang công khai của bạn có nạp https://eotp.com.vn/sdk/eotp-1.js. Bot của eOTP tải trang, thấy SDK thì mở DEVELOPER.
  • Website, nút hiện sẵn: nhúng nút eOTP (iframe /embed/<api_key>) lên trang thật chạy HTTPS trên tên miền đã khai báo hoặc tên miền con. Lượt tải iframe đúng tên miền chỉ ghi nhận ứng viên; bot của eOTP sau đó tải trang công khai của bạn, thấy đường dẫn /embed/<api_key> trên trang thì mở DEVELOPER.
  • Ứng dụng: thêm SDK kiểm license (EotpLicense.kt cho Android, EotpLicense.swift cho iOS) và gọi check() một lần mỗi khi app mở. DEVELOPER tự mở khi đủ hai điều kiện: lượt kiểm license đúng app id từ ít nhất 2 mạng khác nhau (ví dụ Wi‑Fi rồi 4G; hai máy cùng một Wi‑Fi chỉ tính là một mạng), và ít nhất 1 mã OTP thật được máy chủ của bạn gửi và kiểm qua API (/otp/send rồi /otp/verify). Mã gửi ở Chế độ thử không được tính.

Vào dự án ▸ tab API key & tích hợp, ở ô Cách tích hợp chọn Nút hiện sẵn, rồi chọn kiểu nút: Logo + chữ, Logo + chữ bo tròn, Chỉ chữ, Chỉ chữ bo tròn, Chỉ logo hoặc Viền; chọn giao diện tối hoặc sáng và cỡ 28, 32 hoặc 36 px. Phần xem trước và đoạn mã đổi theo lựa chọn của bạn; bấm Lưu để giữ lựa chọn cho dự án, rồi chép mã dán vào trang có ô nhập OTP. Mã đã có sẵn API key và đúng rộng × cao của nút. Trang phải chạy trên tên miền đã khai báo hoặc tên miền con của nó.

HTML
<iframe src="https://eotp.com.vn/embed/eo_live_xxxxxxxxxxxxxxxx?lang=vi&style=logo-text&theme=dark&size=32" width="153" height="32" style="border:0" loading="lazy" referrerpolicy="no-referrer-when-downgrade" title="Xác thực eOTP"></iframe>

Thuộc tính referrerpolicy giúp eOTP biết đúng trang đang nhúng nút để xác nhận tích hợp.

Tham số trên đường dẫn iframe (thiếu hoặc sai giá trị thì eOTP dùng lựa chọn bạn đã lưu cho dự án; chưa lưu thì dùng kiểu mặc định của eOTP):

Tham sốGiá trịGhi chú
langvi, enNgữ của chữ trên nút. Thiếu hoặc khác hai giá trị này thì dùng en.
stylelogo-text, logo-text-pill, text, text-pill, icon, outlineLần lượt: Logo + chữ, Logo + chữ bo tròn, Chỉ chữ, Chỉ chữ bo tròn, Chỉ logo, Viền.
themedark, lightdark: nền đen chữ trắng; light: nền trắng chữ đen. Kiểu Viền: dark viền và chữ đen (cho nền sáng), light viền và chữ trắng (cho nền tối).
size28, 32, 36Chiều cao nút (px), cũng là height của iframe.

Tự đổi tham số thì đổi luôn width và height của iframe cho khớp (nút luôn giãn kín khung):

KiểuRộng (px), lang=vi, cỡ 28 · 32 · 36Rộng (px), lang=en, cỡ 28 · 32 · 36
logo-text, logo-text-pill, outline140 · 153 · 159151 · 166 · 172
text, text-pill81 · 89 · 9162 · 69 · 70
icon28 · 32 · 3628 · 32 · 36

height luôn bằng cỡ. Mã cũ 180 × 40 vẫn hoạt động (hiện nút theo lựa chọn đã lưu của dự án); bạn nên thay bằng mã mới để nút khít khung. Tạo API key mới ở tab API key & tích hợp thì đường dẫn /embed/<api_key> cũ không còn dùng được: bạn chép lại mã nhúng và thay trên website.

Referrer-Policy. eOTP nhận ra website của bạn qua header Referer mà trình duyệt gửi khi tải iframe. Nếu trang đặt Referrer-Policy: no-referrer (header hoặc thẻ <meta name="referrer">), lượt nhúng sẽ không được ghi nhận. Giữ nguyên thuộc tính referrerpolicy="no-referrer-when-downgrade" có sẵn trong mã iframe.

Bấm nút mở popup

Nạp SDK https://eotp.com.vn/sdk/eotp-1.js trên trang có nút và đăng ký eOTP.onButton: khi người dùng bấm nút eOTP, hàm của bạn chạy, gửi số điện thoại lên máy chủ của bạn để tạo phiên, rồi mở popup bằng eOTP.open. Mã mẫu đủ hai phần:

HTML + JavaScript
<!-- 1. Nút -->
<iframe src="https://eotp.com.vn/embed/eo_live_xxxxxxxxxxxxxxxx?lang=vi&style=logo-text&theme=dark&size=32" width="153" height="32" style="border:0" loading="lazy" referrerpolicy="no-referrer-when-downgrade" title="Xác thực eOTP"></iframe>
<!-- 2. Bấm nút → mở popup -->
<script src="https://eotp.com.vn/sdk/eotp-1.js"></script>
<script>
eOTP.onButton(function () {
  var phone = document.querySelector('#phone').value;            // số người dùng nhập trên trang của bạn
  fetch('/your-server/eotp-session', { method: 'POST', body: new URLSearchParams({ phone: phone }) })
    .then(function (r) { return r.json(); })
    .then(function (d) {
      eOTP.open({ session: d.session, onVerified: function (r) { /* gửi r.result về máy chủ để xác nhận */ } });
    });
});
</script>
HàmMô tả
eOTP.onButton(handler)Đăng ký hàm handler chạy khi người dùng bấm nút eOTP trên trang (chuột, Enter hoặc Space). Gọi lại với hàm khác thì hàm mới thay hàm cũ; gọi eOTP.onButton(null) để gỡ (sau khi gỡ, bấm nút không làm gì cho tới khi bạn đăng ký lại). Đăng ký trước hay sau khi iframe nút tải xong đều được.

Trang không đăng ký eOTP.onButton thì nút mở trang eOTP ở tab mới.

Đường dẫn /your-server/eotp-session là máy chủ của bạn: nhận số điện thoại, gọi POST /popup/session rồi trả session cho trang. Khi onVerified chạy, gửi r.result lên máy chủ để gọi POST /popup/result trước khi cho người dùng đi tiếp. Hai lệnh này y hệt kiểu popup: xem Popup xác thực (bước 1 và bước 3). Trang có nút phải chạy HTTPS trên tên miền đã xác minh của dự án hoặc tên miền con của nó.

API key là định danh công khai, nhúng vào iframe hay app đều an toàn. Không bao giờ đưa Secret key vào app hay trang web: gửi và kiểm OTP vẫn đi qua máy chủ của bạn.

SDK gọi GET https://eotp.com.vn/api/v1/license?app=<package|bundle> kèm header X-Api-Key. Thành công trả về:

200 OK
{
  "ok": true,
  "licensed": true,
  "project": "PRJ-XXXXXX",
  "tier": 2,
  "unlocked_now": true,
  "evidence": {
    "pings_ok": 3,
    "subnets": 2,
    "otp_verified": 1,
    "required": { "subnets": 2, "otp_verified": 1 }
  }
}

unlocked_now: true nghĩa là dự án vừa được mở DEVELOPER ở chính lần gọi này. evidence cho biết bằng chứng đã có so với mức cần (required); otp_verified chỉ đếm mã thật, không đếm mã gửi ở Chế độ thử. SDK trả hai trường này trong Result: unlockedNow và evidence (pingsOk, subnets, otpVerified, requiredSubnets, requiredOtpVerified); lần gọi lỗi thì cả hai là null/nil. Chỉ gửi API key, không gửi Secret key từ app.

errorHTTPÝ nghĩa và nên làm
missing_app400Thiếu tham số app. Truyền package (Android) hoặc bundle id (iOS).
unknown_key404API key không tồn tại. Sao chép lại khoá ở tab API key & tích hợp của dự án.
app_mismatch403App id không khớp với ứng dụng trên cửa hàng đã khai báo cho dự án. Kiểm applicationId / bundle id của bản phát hành.
project_not_approved403Dự án chưa được duyệt hoặc đang tạm ngưng. Xem trạng thái ở trang dự án.
store_unavailable503Chưa đọc được app id từ cửa hàng. Thử lại sau (header Retry-After).
rate_limited429Gọi quá nhiều lần trong thời gian ngắn (theo IP hoặc tổng của dự án). Chỉ cần gọi một lần khi app khởi động.

Kênh, giá và hạn mức

4 chế độ gửi

channelCách gửiGiá mỗi tin (ví VND · ví USD)
zaloTin Zalo ZNS từ OA của ViHAT. Số không dùng Zalo sẽ không nhận được.1,550đ · $0.07
smsSMS brandname.2,000đ · $0.09
bothGửi cả Zalo và SMS cùng một mã.Zalo + SMS
zalo_then_smsGửi Zalo trước; Zalo không tới được thì tự gửi SMS cùng mã. Khuyến nghị.Giữ Zalo + SMS; hoàn phần SMS khi Zalo tới máy

Giá như nhau ở mọi gói trả phí; gói FREE không trừ ví. Lần gửi thất bại được hoàn tiền ngay, coi như không trừ. Ví chỉ đủ cho Zalo thì zalo_then_sms tự hạ về zalo. Gói FREE chỉ gửi một kênh: both và zalo_then_sms tự chuyển về kênh mặc định của dự án (zalo hoặc sms, chọn ở Cài đặt gửi), không báo lỗi.

Quốc gia người nhận

Mỗi nước có cờ kênh riêng (xem countries trong GET /pricing). Khi gửi, eOTP đọc mã nước của số nhận: nước chưa có Zalo thì gửi sms, nước chưa có SMS thì gửi zalo (phản hồi có channel_note); nước không có kênh nào trả channel_not_supported_in_country; nước chưa mở trả country_not_supported.

Các gói và hạn mức

Hạn mức tính theo TÀI KHOẢN: mọi dự án của bạn dùng chung một bể, theo gói cao nhất trong các dự án.

FREE · Cấp 1

100 OTP/tháng · tối đa 20 OTP/ngày

Miễn phí, không trừ ví, một kênh. Có ngay khi dự án được duyệt.

DEVELOPER · Cấp 2

100,000 OTP/tháng · tối đa 10,000 OTP/ngày

Trừ ví theo tin. Tự lên khi bạn kích hoạt DEVELOPER.

UNLIMITED · Cấp 3

Không giới hạn

Trừ ví theo tin. Đội ngũ ViHAT mở sau khi làm việc với bạn (hotline +84 901 888 484).

BUSINESS · Cấp 4

Hạn mức riêng theo hợp đồng

Trừ ví theo tin. ViHAT đặt hạn mức tháng và ngày riêng cho tài khoản của bạn.

Tháng và ngày tính theo giờ UTC, làm mới lúc 00:00 UTC. Một OTP được tính hạn mức khi có ít nhất một lần gửi không thất bại. Hết hạn mức tháng, send trả quota_exceeded; hết hạn mức ngày trả daily_quota_exceeded (HTTP 429).

Giá và quốc gia

GEThttps://eotp.com.vn/api/v1/pricing

Bảng giá, hạn mức các gói, gói nạp và danh sách nước hỗ trợ. Công khai: không cần khoá, mở CORS (Access-Control-Allow-Origin: *) nên gọi được cả từ trình duyệt. Phản hồi được cache 300 giây (Cache-Control: public, max-age=300) và có ETag: gửi If-None-Match để nhận 304 khi chưa đổi.

Yêu cầu
curl https://eotp.com.vn/api/v1/pricing
200 OK (rút gọn)
{
  "ok": true,
  "currency": "VND",
  "price_zalo_vnd": 1550,
  "price_sms_vnd": 2000,
  "tiers": {
    "1": { "name": "FREE", "monthly_quota": 100, "daily_quota": 20 },
    "2": { "name": "DEVELOPER", "monthly_quota": 100000, "daily_quota": 10000 },
    "3": { "name": "UNLIMITED", "monthly_quota": null, "daily_quota": null }
  },
  "topup_packages": [{ "amount": 100000, "bonus_pct": 0 }],
  "countries": [
    { "iso": "VN", "name_en": "Vietnam", "name_vi": "Việt Nam", "dial": "84", "sms": true, "zalo": true, "enabled": true }
  ],
  "updated_at": 1794268800
}

monthly_quota/daily_quota bằng null là không giới hạn. Nước có enabled: false chưa nhận gửi OTP. dial là giá trị dùng cho country_code.

Ví và tiền tệ

  • Mỗi loại tiền một ví riêng. Tài khoản có ví VND (tính bằng đồng) và ví USD (tính bằng cent). Chỉ một ví đang dùng (currency trong project); ví kia được giữ nguyên, không quy đổi. Mọi số tiền trong API ở đơn vị nhỏ nhất của ví đang dùng.
  • Ví gốc và ví tặng. Mỗi ví gồm phần gốc (tiền bạn nạp) và phần tặng (khuyến mãi của gói nạp, mã khuyến mãi). Khi gửi OTP, eOTP trừ ví tặng trước, hết mới trừ ví gốc; hoàn tiền trả về đúng ví đã trừ.
  • Trừ ngay khi nhận yêu cầu, hoàn khi thất bại. Tiền được trừ cùng lúc kiểm số dư, nên gọi song song không làm âm ví. Gửi thất bại được hoàn ngay; zalo_then_sms hoàn phần SMS khi Zalo tới máy.
  • Nạp tiền theo gói ở trang Ví của cổng developer. Chuyển khoản VietQR nạp vào ví VND, mã QR có hạn 30 phút. Gói nạp hiện hành có trong topup_packages của GET /pricing.
  • Mã khuyến mãi nhập khi tạo yêu cầu nạp; mỗi tài khoản dùng mỗi mã một lần, tiền khuyến mãi vào ví tặng khi khoản nạp được xác nhận.

Gói FREE không trừ ví. Số dư không đủ cho một tin thì send trả 402 insufficient_balance kèm currency, balance, cost.

Mã lỗi

Toàn bộ mã lỗi của API v1. Cột "Ý nghĩa và nên làm" là cách xử lý khuyến nghị trên máy chủ của bạn.

errorHTTPEndpointÝ nghĩa và nên làm
invalid_api_key401có khoáThiếu hoặc sai API key. Kiểm biến môi trường EASYOTP_API_KEY.
invalid_api_secret401có khoáAPI key đúng nhưng thiếu hoặc sai secret. Kiểm header X-Api-Secret; mất secret thì tạo secret mới ở trang dự án.
legacy_auth_expired401có khoáKhoá cũ một khoá đã quá 30 ngày chuyển đổi. Tạo secret mới rồi gửi đủ 2 header.
auth_rate_limited429có khoáXác thực sai nhiều lần; thử lại sau. Sửa khoá rồi đợi retry_in giây.
project_not_active403có khoáDự án chưa duyệt, bị từ chối, tạm ngưng hoặc tài khoản bị khoá. Có thêm status, tier.
rate_limited429license, pricing, yêu cầu thiếu/sai khoáQuá nhiều yêu cầu chưa xác thực; thử lại sau. Yêu cầu có khoá + secret đúng không gặp lỗi này. Đợi retry_in giây (header Retry-After).
rate_limit_unavailable503tất cảHệ thống tạm lỗi. Thử lại sau vài giây.
method_not_allowed405tất cảSai phương thức: send/verify dùng POST; status, project, license, pricing dùng GET.
bad_request400send, verifyBody không phải JSON hợp lệ.
invalid_callback_url400sendcallback_url sai dạng (bad_url), không phải HTTPS (not_https), khác cổng 443 (bad_port) hoặc trỏ về IP nội bộ (private_host). Sửa URL theo reason rồi gửi lại.
invalid_phone400sendSố không hợp lệ (reason cho biết lý do). Nhắc người dùng nhập lại; gửi dạng E.164 hoặc kèm country_code.
country_not_supported400sendNước của số chưa được mở (country_code). Báo người dùng dùng số của nước được hỗ trợ.
channel_not_supported_in_country400sendNước của số chưa có kênh nào, hoặc kênh duy nhất của nước đó đang tạm dừng. Có country_code, country_iso.
invalid_channel400sendKênh không hợp lệ hoặc tạm dừng. channels_enabled liệt kê kênh dùng được.
quota_exceeded429sendHết hạn mức tháng của bể tài khoản (month_used/month_quota). Lên gói cao hơn hoặc chờ sang tháng.
daily_quota_exceeded429sendHết hạn mức ngày của bể tài khoản (today_used/daily_quota). resets_at là mốc Unix làm mới, retry_after là số giây còn lại.
insufficient_balance402sendVí không đủ (balance < cost, đơn vị theo currency). Nạp thêm tiền.
unavailable503sendDịch vụ gửi tạm thời chưa sẵn sàng. Thử lại sau vài phút.
send_failed502sendKhông gửi được tới số này (ví dụ số không dùng Zalo khi chọn zalo). Thử sms hoặc zalo_then_sms.
wrong_code400verifyMã sai, còn attempts_left lần.
expired410verifyMã hết hạn hoặc đã dùng. Gửi mã mới.
too_many_attempts429verifySai 5 lần, mã bị huỷ. Gửi mã mới.
not_found404verify, statusrequest_id không đúng, thuộc dự án khác hoặc phone không khớp.
unknown_key404licenseAPI key không tồn tại. Sao chép lại ở tab Tích hợp.
missing_app400licenseThiếu hoặc sai tham số app.
app_mismatch403licenseApp id không khớp ứng dụng đã khai báo.
project_not_approved403licenseDự án chưa duyệt hoặc đang tạm ngưng.
store_unavailable503licenseChưa đọc được app id từ cửa hàng. Thử lại sau.
server_error500tất cảLỗi hệ thống. Thử lại sau, gửi kèm X-Request-Id khi liên hệ hỗ trợ.

Hạn mức gói và thông số mã

Mỗi gói có hạn mức OTP theo tháng và theo ngày, tính chung cho mọi dự án của tài khoản (xem Kênh, giá và hạn mức và GET /pricing). Vượt hạn mức, send trả HTTP 429:

Mã lỗiHTTPÝ nghĩaCách xử lý
quota_exceeded429Hết hạn mức tháng của gói (month_used/month_quota).Nâng gói để gửi tiếp, hoặc chờ sang tháng (00:00 UTC ngày 1).
daily_quota_exceeded429Hết hạn mức ngày của gói (today_used/daily_quota). Có header Retry-After.Nâng gói, hoặc chờ tới resets_at (retry_after giây).

Cần hạn mức cao hơn gói hiện có, gọi hotline +84 901 888 484.

Thông số mãMứcKhi vượt
Nhập sai mã5 lần cho mỗi mã429 too_many_attempts, mã bị huỷ
Thời gian sống của mã60–900 giây theo cài đặt dự án, mặc định 300410 expired
Độ dài mã4–10 ký tự theo cài đặt dự án—

Hàng đợi thay cho từ chối. Ở chế độ hàng đợi, lúc đông eOTP vẫn nhận yêu cầu send, chỉ tăng eta_ms; không có trần độ dài hàng đợi. Hạn mức gói ở trên vẫn áp dụng.

Ví dụ code

Mọi ví dụ chạy trên máy chủ, đọc khoá từ biến môi trường EASYOTP_API_KEY và EASYOTP_API_SECRET, gửi đủ 2 header X-Api-Key + X-Api-Secret. Mỗi ví dụ gửi mã rồi kiểm mã; chạy được với cả phản hồi hàng đợi lẫn đồng bộ của send.

Dùng để thử nhanh từ dòng lệnh.

curl
# 1. Gửi mã
curl -X POST https://eotp.com.vn/api/v1/otp/send \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"phone":"0912345678","country_code":"84"}'

# 2. (Tuỳ chọn) Tra trạng thái gửi
curl "https://eotp.com.vn/api/v1/otp/status?request_id=3f9a1c0e8b7d4a26b5e0c9d18f2a7b64" \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET"

# 3. Kiểm mã (thay request_id và code)
curl -X POST https://eotp.com.vn/api/v1/otp/verify \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"request_id":"3f9a1c0e8b7d4a26b5e0c9d18f2a7b64","code":"123456"}'

# 4. Xem hạn mức và số dư
curl https://eotp.com.vn/api/v1/project \
  -H "X-Api-Key: $EASYOTP_API_KEY" \
  -H "X-Api-Secret: $EASYOTP_API_SECRET"

Luồng khuyến nghị

  1. Người dùng nhập số điện thoại

    Ứng dụng hoặc trang web gửi số lên máy chủ của bạn (không gọi thẳng eOTP).

  2. Máy chủ gọi send

    Trước khi gọi, máy chủ tự chặn lạm dụng theo IP/thiết bị của người dùng (ví dụ captcha, giới hạn số lần). Lưu request_id và phone_e164 vào phiên của người dùng. Dùng code_type để mở bàn phím số hay chữ cho ô nhập.

  3. Người dùng nhập mã

    Hiện ô nhập mã và đếm ngược theo expires_in. Muốn báo "đã gửi" chính xác, tra otp/status; gặp failed thì gợi ý gửi lại qua kênh khác. Nút "Gửi lại" nên mở sau một khoảng chờ để người dùng kịp nhận tin.

  4. Máy chủ gọi verify

    Lấy request_id từ phiên (không tin giá trị do ứng dụng gửi lên), gửi kèm mã người dùng nhập.

  5. Xử lý kết quả

    verified: true thì đăng nhập/xác nhận và xoá request_id khỏi phiên. wrong_code cho nhập lại; expired hoặc too_many_attempts thì gửi mã mới.

Trên website, bạn không cần tự dựng bước 3–5: dùng Popup xác thực, eOTP hiện ô nhập mã, gửi lại và báo lỗi, máy chủ của bạn chỉ gọi popup/session rồi popup/result.

Bảo mật

  • Giữ Secret key trên máy chủ, trong biến môi trường hoặc kho bí mật. Không đưa vào app, JavaScript phía trình duyệt, ảnh chụp màn hình hay kho mã. API key một mình chỉ là định danh, nhúng iframe/SDK được.
  • Không ghi mã OTP vào log. eOTP không bao giờ trả mã qua API; bạn cũng không ghi mã người dùng nhập vào log hay công cụ theo dõi lỗi.
  • Không ghi Secret key vào log. Che khoá khi in cấu hình hoặc lỗi.
  • Để thời gian sống ngắn. 5 phút là đủ cho đa số luồng; chỉnh 1–15 phút trong cài đặt dự án.
  • Chặn lạm dụng ở phía bạn. Giới hạn của eOTP tính theo dự án và theo IP máy chủ của bạn, không theo người dùng cuối: bạn nên giới hạn thêm theo IP, thiết bị, tài khoản để kẻ xấu không dùng hết hạn mức.
  • Đổi secret khi nghi lộ. Tạo secret mới trong tab Tích hợp, secret cũ mất hiệu lực ngay. Cập nhật máy chủ trước để không dính auth_rate_limited.

Cần hỗ trợ tích hợp? Gọi hotline +84 901 888 484.

Thay đổi

11/11/2026

  • Callback về máy chủ của bạn có thêm header X-Eotp-Timestamp; X-Eotp-Signature nay là HMAC-SHA256 của timestamp + "." + thân thô (chữ ký chỉ trên thân không còn dùng). Bạn cập nhật đoạn kiểm theo Xác minh chữ ký và từ chối lời gọi lệch quá 300 giây.

10/11/2026

  • Nút hiện sẵn gọi được popup: nạp /sdk/eotp-1.js và đăng ký eOTP.onButton(handler), người dùng bấm nút eOTP là hàm của bạn chạy để tạo phiên và mở eOTP.open. Trang không đăng ký thì nút mở trang eOTP như cũ. Xem Bấm nút mở popup.
  • Thêm Popup xác thực cho website, cách tích hợp mặc định: máy chủ gọi POST /popup/session, trang nạp /sdk/eotp-1.js và gọi eOTP.open, máy chủ xác nhận bằng POST /popup/result. Số lần gửi lại trong popup đặt ở Cài đặt gửi của dự án. Lỗi mới popup_not_supported, invalid_result.
  • Nút nhúng /embed/<api_key> có 6 kiểu, giao diện tối/sáng và cỡ 28/32/36: chọn ở tab API key & tích hợp của dự án, hoặc truyền style, theme, size trên đường dẫn iframe. Mã cũ 180 × 40 vẫn hoạt động; bạn nên thay bằng mã mới để nút khít khung.
  • otp/send có chế độ hàng đợi: trả ngay "status": "queued" và eta_ms; lúc đông vẫn nhận yêu cầu, chỉ tăng eta_ms.
  • Thêm GET /otp/status để tra trạng thái gửi (queued, sending, sent, failed, expired).
  • otp/send nhận callback_url (tuỳ chọn): eOTP POST kết quả gửi về máy chủ của bạn, ký bằng X-Eotp-Signature, gửi lại tối đa 3 lần. Lỗi mới invalid_callback_url.

9/11/2026

  • Xác thực bằng 2 khoá: X-Api-Key + X-Api-Secret (hoặc Bearer key:secret). Khoá cũ một khoá còn dùng 30 ngày kèm header Deprecation, sau đó 401 legacy_auth_expired.
  • Chặn IP tạm thời khi xác thực sai nhiều lần liên tiếp: 429 auth_rate_limited.
  • Số điện thoại nhiều nước: phone dạng E.164 hoặc nội địa + country_code; phản hồi có phone_e164. Lỗi mới country_not_supported, channel_not_supported_in_country; kênh đổi theo nước có channel_note.
  • Kiểu mã alnum (chữ + số, so không phân biệt hoa/thường) và độ dài 4–10 đặt trong Cài đặt gửi của dự án.
  • Hạn mức tháng và ngày tính theo bể tài khoản (mọi dự án dùng chung); thêm daily_quota_exceeded. Gói FREE gửi một kênh.
  • Ví theo loại tiền (VND, USD) và ví tặng: thêm currency, cost, balance ở send và balance_minor ở project; các trường *_vnd giữ cho tương thích.
  • license trả evidence; DEVELOPER mở khi đủ bằng chứng tích hợp thật.

Quốc gia hỗ trợ

Quốc giaSMSZalo
Việt NamCóCó
SingaporeKhôngKhông
Thái LanKhôngKhông
MalaysiaKhôngKhông
IndonesiaKhôngKhông
PhilippinesKhôngKhông
Nhật BảnKhôngKhông
Hàn QuốcKhôngKhông
Hoa KỳKhôngKhông
ÚcKhôngKhông