EKYC Middleware — Tài liệu Kỹ thuật Tích hợp
Ngày xuất: 05/10/2026
Hướng dẫn tích hợp Webview FE
Tài liệu FE tích hợp EKYC Middleware
Tài liệu bàn giao cho team Frontend (Webview) — hướng dẫn từng bước call API, kèm curl, params và response đầy đủ.
1. Tổng quan luồng
FE (Webview) BE Middleware Core EKYC (AccessTrade)
│ │ │
│ 1. Test session (auto-auth) │ │
│───────────────────────────────────>│ │
│ ← session_id + webview_token │ │
│ │ │
│ 2. Set webview_token │ │
│ Authorization: Bearer <token> │ │
│ │ │
│ 3. init │ │
│───────────────────────────────────>│──── FPT SDK URL (nếu có config) ──>│
│ 4. submit-identity │ │
│───────────────────────────────────>│ │
│ 5. submit-face │ │
│───────────────────────────────────>│ │
│ 6. submit-ekyc-result │──── tạo hợp đồng (nếu EKYC OK) ───>│
│───────────────────────────────────>│ │
│ 7. sign-contract │ │
│───────────────────────────────────>│ │
│ ← contract_ref + status │ │
- Base URL (dev):
https://at-product-family-econtract-api.dev.oneat.org - Toàn bộ request/response đều là JSON (
Content-Type: application/json) - Webview API yêu cầu header
Authorization: Bearer <webview_token>
2. Chuẩn bị dữ liệu partner
Chạy 1 lần trên server (seed 3 partner: VPBANK, TPBANK, AT):
php artisan partner:seed
Partner AT dùng nội bộ để FE test. Lệnh an toàn khi chạy lại (không ghi đè dữ liệu cũ).
3. Format response chuẩn
Mọi API trả về đúng format:
{
"status": "success",
"data": { "...": "nội dung nghiệp vụ" },
"meta": {
"request_id": "uuid",
"timestamp": "2026-08-07T00:00:00+00:00",
"response_time_ms": 15
},
"code": null,
"message": null
}
status:successhoặcerrordata: payload nghiệp vụ- Khi lỗi:
code= mã lỗi (ví dụAUTHENTICATION_INVALID),message= mô tả,data= null
4. Bước 1 — Tạo test session (auto xác thực)
API này không cần JWT — 1 lần call là có đủsession_id+webview_tokenđể FE set vào webview và test toàn bộ luồng. Chỉ hoạt động ở môi trường khác production.
Request
POST /api/v1/partner/test-session
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
user_id | string | ✅ | Mã user bên partner |
partner_code | string | ❌ | Mã partner, mặc định AT |
partner_tx_id | string | ❌ | Mã giao dịch partner, tự sinh TEST_... nếu bỏ trống |
account_number | string | ❌ | Số tài khoản |
identity_info | object | ❌ | Thông tin sẵn (email, full_name...) |
expired_minutes | int | ❌ | Thời hạn session (1-1440), mặc định 30 |
Curl
curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/partner/test-session \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"user_id": "USER_001",
"partner_code": "AT",
"identity_info": {
"email": "user001@test.com",
"full_name": "Nguyễn Văn A"
}
}'
Response 200
{
"status": "success",
"data": {
"session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
"webview_url": "https://ekyc-middleware.test/webview/ekyc_CTBi1kLoVsZCV2DsC0Yz",
"webview_token": "buvwE2KukoEEbhYiUYGsCQESf1X2XBqClF9xFXiZPRWlpSPRmQLUr5UbmbwLT4kH",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJwYXJ0bmVyX2NvZGUiOiJBVCIs...",
"access_token_expires_at": "2026-08-07T00:15:48+00:00",
"expires_in": 3600,
"refresh_token": "PcmxzgZE1njKbMy0tGGt6o1gioyc9HR9R9F1vPiAxKHZuiJaRUvKeyW8DWYwG7RG",
"refresh_token_expires_at": "2026-08-13T23:15:48+00:00",
"session_expired_at": "2026-08-06T23:45:48+00:00"
},
"meta": {
"request_id": "25258191-cbe0-4bf5-9457-9f094b754690",
"timestamp": "2026-08-06T23:15:49+00:00",
"response_time_ms": 244
}
}
Trường quan trọng cho FE
| Trường | Dùng để |
|---|---|
webview_token | Set vào header Authorization: Bearer <webview_token> cho mọi call webview API |
session_id | Truyền trong body session_id của webview API |
webview_url | URL load webview (thường .../webview/{session_id}) |
access_token | JWT RS256 (dự phòng cho partner flow) |
session_expired_at | Hạn session — hết hạn thì phải tạo session mới |
5. Webview API (dùng webview_token)
Bắt buộc: mọi request webview phải có headerAuthorization: Bearer <webview_token>(lấy từ bước 1). Thiếu/sai token →401.
5.1. Init webview
POST /api/v1/webview/init
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
session_id | string | ❌ | Không bắt buộc — session tự xác định qua Authorization: Bearer <webview_token> |
email | string | ❌ | Email từ bước nhập email trên UI — cần có để Core phát ekyc_sdk_url / ekyc_sdk_token |
Curl
curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/init \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <webview_token>" \
-d '{"email": "user001@test.com"}'
Response 200
{
"status": "success",
"data": {
"session_id": "ekyc_pFPmNibARmmDopXXd3kI",
"partner_code": "AT",
"user_id": "USER_002",
"status": "processing",
"identity_info": { "email": "b@test.com", "full_name": "Trần Văn B" },
"contract_ref": "TEST_WCQHIVOB96WY",
"expired_at": "2026-08-07T00:22:43+00:00",
"ekyc_sdk_url": null,
"ekyc_sdk_token": null,
"ekyc_sdk_token_name": null,
"ekyc_sdk_access_token": null,
"ekyc_sdk_resource_id": "4320855b-d9da-4f6f-9807-6b1744bcea37"
},
"meta": { "request_id": "...", "timestamp": "...", "response_time_ms": 15 }
}
ekyc_sdk_url/ekyc_sdk_token/ekyc_sdk_token_name/ekyc_sdk_access_token: URL + token + tên token + access_token FPT SDK từ Core EKYC — chỉ trả về khi cócore_access_key. Nếunullthì FE dùng SDK/UI tự quản lý (hoặc chưa gửi email).ekyc_sdk_resource_id: UUIDresource_idmà BE đã gửi lên Core cho phiên SDK này — dùng để đối chiếu/khớp session FPT nếu cần.
5.2. Nộp giấy tờ
POST /api/v1/webview/submit-identity
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
session_id | string | ❌ | Không bắt buộc — xác định qua webview_token |
identity_data.front_image_url | string(url) | ✅ | Ảnh mặt trước CMND/CCCD |
identity_data.back_image_url | string(url) | ✅ | Ảnh mặt sau CMND/CCCD |
identity_data.id_number | string | ✅ | 9-12 chữ số |
identity_data.full_name | string | ✅ | Họ tên |
identity_data.date_of_birth | string | ✅ | Định dạng Y-m-d |
Curl
curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/submit-identity \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <webview_token>" \
-d '{
"session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
"identity_data": {
"front_image_url": "https://cdn.test/front.jpg",
"back_image_url": "https://cdn.test/back.jpg",
"id_number": "079201012345",
"full_name": "Nguyễn Văn A",
"date_of_birth": "1990-01-01"
}
}'
Response 200
{
"status": "success",
"data": { "status": "processing", "next_step": "face_match" },
"meta": { "...": "..." }
}
5.3. Nộp khuôn mặt
POST /api/v1/webview/submit-face
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
session_id | string | ❌ | Không bắt buộc — xác định qua webview_token |
face_data.face_image_url | string(url) | ✅ | Ảnh khuôn mặt |
face_data.liveness_video_url | string(url) | ❌ | Video liveness |
Curl
curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/submit-face \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <webview_token>" \
-d '{
"session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
"face_data": { "face_image_url": "https://cdn.test/face.jpg" }
}'
Response 200
{
"status": "success",
"data": {
"face_matched": true,
"liveness_passed": true,
"score": 95.5,
"next_step": "ekyc_result"
},
"meta": { "...": "..." }
}
5.4. Gửi kết quả EKYC
POST /api/v1/webview/submit-ekyc-result
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
session_id | string | ❌ | Không bắt buộc — xác định qua webview_token |
core_ekyc_result.tx_id | string | ✅ | Mã giao dịch Core EKYC |
core_ekyc_result.status | string | ✅ | completed hoặc failed |
core_ekyc_result.identity_verified | bool | ❌ | |
core_ekyc_result.face_matched | bool | ❌ | |
core_ekyc_result.liveness_passed | bool | ❌ | |
core_ekyc_result.score | number | ❌ | 0-100 |
Curl
curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/submit-ekyc-result \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <webview_token>" \
-d '{
"session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
"core_ekyc_result": {
"tx_id": "CORE_TX_001",
"status": "completed",
"identity_verified": true,
"face_matched": true,
"liveness_passed": true,
"score": 95.5
}
}'
Response 200 (Thành công)
{
"status": "success",
"data": {
"session_status": "awaiting_sign",
"contract_available": true,
"contract_ref": "550e8400-e29b-41d4-a716-446655440000"
},
"meta": { "...": "..." }
}
Lưu ý: -contract_available:truekhi đã tạo hợp đồng thành công trên Core,falsekhi partner chưa cấu hình Core hoặc chưa có hợp đồng. -contract_ref: Mãreference_idtừ Core EKYC. - Nếu quá trình tạo HĐ trên Core bị lỗi, API sẽ trả vềHTTP 400với mã lỗiCONTRACT_CREATION_FAILED: ``json { "status": "error", "code": "CONTRACT_CREATION_FAILED", "message": "Xác thực EKYC thành công nhưng tạo hợp đồng thất bại: <chi tiết lỗi từ Core>", "data": null }``
5.5. Ký hợp đồng
POST /api/v1/webview/sign-contract
| Param | Type | Bắt buộc | Mô tả |
|---|---|---|---|
session_id | string | ❌ | Không bắt buộc — xác định qua webview_token |
sign_method | string | ✅ | otp / usb_token / esign |
sign_data.signed_content | string | ✅ | Nội dung đã ký (base64/hash) |
sign_data.otp_ref | string | ❌ | OTP reference nếu ký bằng OTP |
Curl
curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/sign-contract \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <webview_token>" \
-d '{
"session_id": "ekyc_CTBi1kLoVsZCV2DsC0Yz",
"sign_method": "otp",
"sign_data": { "signed_content": "base64...", "otp_ref": "OTP_001" }
}'
Response 200
{
"status": "success",
"data": {
"contract_ref": "HD/2026/08/06/2",
"status": "signed",
"signed_at": "2026-08-06T23:52:11+00:00"
},
"meta": { "...": "..." }
}
5.6. Xem thông tin chi tiết hợp đồng
Dùng để kiểm tra trạng thái hợp đồng và lấy link ký trực tiếp trên cổng FPT eContract.
GET /api/v1/webview/contract/{session_id}
Curl
curl -X GET https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/contract/ekyc_CTBi1kLoVsZCV2DsC0Yz \
-H "Accept: application/json" \
-H "Authorization: Bearer <webview_token>"
Response 200
{
"status": "success",
"data": {
"contract_ref": "EKYCC_139",
"status": "pending",
"core_status": "WAITING_TO_SIGN",
"link": "https://demo.econtract.kyta.fpt.com/signing/signature-process/000010zGi2lPcpexk8RKGFlQ6/p_002_r_001?jwt=eyJhbGci...",
"signed_at": null
},
"meta": { "request_id": "...", "timestamp": "...", "response_time_ms": 25 }
}
Lưu ý: -link: Đường dẫn trực tiếp để mở trang ký hợp đồng FPT trên WebView hoặc iframe. -status: Trạng thái chuẩn hóa (pending,signed,failed,revoked,draft). -core_status: Trạng thái gốc từ Core (WAITING_TO_SIGN,SIGNED,SUBMIT,OVERDUE,VOIDED...).
5.7. Danh sách hợp đồng của user (1 User có nhiều HĐ)
Một user có thể có nhiều hợp đồng trong quá khứ (ví dụ: hợp đồng cũ hết hạn, bị hủy hoặc user tạo phiên mới). API này trả về toàn bộ danh sách hợp đồng của user hiện tại (xác thực qua webview_token), sắp xếp mới nhất lên đầu.
GET /api/v1/webview/contracts
Curl
curl -X GET https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/contracts \
-H "Accept: application/json" \
-H "Authorization: Bearer <webview_token>"
Response 200
{
"status": "success",
"data": {
"user_id": "USER_001",
"total": 2,
"contracts": [
{
"id": 140,
"session_id": 140,
"contract_ref": "EKYCC_140",
"contract_number": "EKYCC_140",
"template_code": "HOP_DONG_USER",
"status": "pending",
"core_status": "WAITING_TO_SIGN",
"link": "https://demo.econtract.kyta.fpt.com/signing/signature-process/...",
"signed_at": null,
"sign_expire_at": "2026-11-16T15:41:07+07:00",
"created_at": "2026-08-18T15:45:00+07:00"
},
{
"id": 139,
"session_id": 139,
"contract_ref": "EKYCC_139",
"contract_number": "EKYCC_139",
"template_code": "HOP_DONG_USER",
"status": "revoked",
"core_status": "OVERDUE",
"link": null,
"signed_at": null,
"sign_expire_at": "2026-08-15T10:00:00+07:00",
"created_at": "2026-08-01T10:00:00+07:00"
}
]
},
"meta": { "request_id": "...", "timestamp": "...", "response_time_ms": 12 }
}
5.8. Upload ảnh (trả link)
FE upload ảnh (CCCD/face) trước, lấyurlrồi truyền vàoidentity_data.front_image_url/back_image_url/face_data.face_image_url.
Request: multipart/form-data
POST /api/v1/webview/upload
Authorization: Bearer <webview_token>
| Field | Type | Bắt buộc | Mô tả |
|---|---|---|---|
file | file (ảnh) | ✅ | Ảnh cần upload, tối đa 10MB |
Curl
curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/webview/upload \
-H "Authorization: Bearer <webview_token>" \
-F "file=@front.jpg"
Response 200
{
"status": "success",
"data": {
"url": "https://at-product-family-econtract-api.dev.oneat.org/storage/uploads/2026/08/xxxx.jpg"
},
"meta": { "...": "..." }
}
Format response cố định (data.url) — khi BE nối hệ thống upload khác (CDN/S3), FE không cần đổi gì.
6. Mã lỗi thường gặp
| HTTP | Code | Nguyên nhân / Xử lý |
|---|---|---|
| 401 | AUTHENTICATION_REQUIRED | Thiếu header Authorization |
| 401 | AUTHENTICATION_INVALID | webview_token sai/hết hạn → tạo test session mới |
| 410 | SESSION_EXPIRED | Session hết hạn (session_expired_at) → tạo session mới |
| 404 | SESSION_NOT_FOUND | Sai session_id |
| 422 | VALIDATION_ERROR | Thiếu/sai param (xem message mô tả chi tiết) |
| 403 | — | Gọi test-session trên môi trường production |
7. Luồng production (tham chiếu cho partner)
Khi tích hợp chính thức, Bank gọi POST /api/v1/partner/init-session với JWT RS256 (sign bằng private key do BE cấp):
curl -X POST https://at-product-family-econtract-api.dev.oneat.org/api/v1/partner/init-session \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer <JWT_RS256>" \
-d '{
"partner_tx_id": "TXN_001",
"user_id": "USER_001",
"account_number": "123456",
"identity_info": { "full_name": "Nguyễn Văn A" }
}'
Response giống hệt test-session (cùng webview_token, session_id, access_token...) — FE không cần đổi logic, chỉ khác cách lấy token.
- Tạo JWT test nội bộ:
php artisan jwt:generate --partner=VPBANK --user=USER_001 - Chi tiết quy chuẩn API/lỗi:
docs/01-api-standards.md - Danh sách đầy đủ endpoints:
docs/02-api-endpoints.md