Webhook tạo member từ landing pageCập nhật 2026-06-10

2 giờ sáng, một khách chốt đơn trên landing page của bạn. Bạn đang ngủ. Đến sáng mở mắt ra: khách đã có tài khoản focus.camp, đã nằm trong community, đã vào đúng challenge, và đã nhận email đăng nhập — không ai phải thao tác gì cả. Đó là việc webhook này làm.

Nó dành cho bạn nếu: bạn thu tiền ở nơi khác (landing page riêng, Ladipage, web bán hàng…) nhưng muốn giao sản phẩm trên focus.camp. Khách không phải thanh toán lại lần hai.

Setup — 3 bước

  1. Vào Settings → Tích hợp → API Keys, bấm + Tạo API key mới, tick ô Provision members.
  2. Copy key (chỉ hiện 1 lần!) và đưa cho dev của bạn — kèm link trang này.
  3. Dev cài đặt: sau khi khách thanh toán thành công, hệ thống của bạn gọi webhook (chi tiết ở phần kỹ thuật bên dưới). Xong.

Từ đó mỗi đơn hàng chạy tự động: tạo tài khoản (nếu khách chưa có) → join community → vào challenge bạn chỉ định → gửi email magic link cho khách mới.

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

Lỡ hệ thống gọi trùng 2 lần thì sao?

Không sao. Mỗi đơn hàng có một mã riêng (externalOrderId) — gọi lại cùng mã thì focus.camp nhận ra và bỏ qua, không tạo member trùng, không gửi email lần hai. Retry thoải mái.

Khách đã có tài khoản focus.camp rồi thì sao?

Hệ thống nhận ra qua email, dùng lại tài khoản cũ của khách — vẫn join community và vào challenge bình thường. Không gửi email magic link (vì khách đã đăng nhập được sẵn).

Khách có bị thu phí gì thêm không?

Không. focus.camp không thu tiền ở bước này — việc thu tiền đã xảy ra ở landing page của bạn rồi. Mặc định khách vào free (Explorer); nếu bạn truyền tierKey + billingPeriod, khách được cấp thẳng gói trả phí tương ứng (có thời hạn) mà vẫn không phải trả thêm gì trên focus.camp.

Key bị lộ thì sao?

Vào tab Tích hợp bấm Revoke — key chết ngay lập tức. Tạo key mới đưa lại cho dev. Key này chỉ tạo được member free (không đụng được nội dung hay tiền), và bị giới hạn 60 lần gọi/phút, nên thiệt hại nếu lộ cũng được khoanh vùng.

Làm sao biết webhook đã chạy?

Member mới sẽ xuất hiện trong danh sách thành viên (và trong challenge nếu có chỉ định). Lịch sử provision được lưu lại để đối soát, kể cả khi key đã xoá.

Phần kỹ thuật — đưa cho dev

Endpoint

POST https://focus.camp/api/integrations/member
Authorization: Bearer fc_live_<KEY>
Content-Type: application/json

Key gắn với 1 community — member luôn được đưa vào community của key, không truyền communityId trong body.

Request body

FieldBắt buộcMô tả
emailEmail khách hàng (≤ 200 ký tự). Trùng email = dùng lại tài khoản cũ.
nameTên hiển thị (1–120 ký tự). Chỉ áp dụng khi tạo tài khoản mới.
externalOrderIdMã đơn hàng phía bạn (1–200 ký tự). Khoá idempotent — gọi lại cùng id sẽ không tạo trùng.
challengeSlugSlug challenge muốn đưa khách vào (bỏ trống = chỉ join community).
tierKeyKey gói trả phí muốn cấp (vd builder, pro). Bỏ trống = free Explorer. Xem key hợp lệ ngay trong Settings → Tích hợp. Đi cùng billingPeriod.
billingPeriodKỳ hạn: weekly · monthly · 3months · 6months · yearly. Thời hạn (số ngày) tự tính theo cấu hình community. Bắt buộc nếu có tierKey.
refCodeMã người giới thiệu (4–16 ký tự chữ/số) để ghi công hoa hồng cho đơn bán ở web ngoài. Sai định dạng thì bị bỏ qua, KHÔNG làm hỏng việc cấp tài khoản.
amountVndSố tiền thực thu (VNĐ) để tính hoa hồng theo %. Nhận cả số lẫn chuỗi số (3000000 hoặc "3000000"). Bỏ trống nếu bạn trả thưởng cố định.

Cấp gói trả phí: truyền tierKey + billingPeriod để khách vào thẳng gói (không chỉ free). Hệ thống tạo subscription ACTIVE với hạn = ngày hiện tại + số ngày của kỳ hạn. Nếu khách đang có gói ACTIVE khác, gói cũ được thay thế bằng gói mới (giống hệt luồng thanh toán trong app). Chỉ chấp nhận kỳ hạn đang được bật ở tab Thanh toán.

Ví dụ

curl -X POST https://focus.camp/api/integrations/member \
  -H "Authorization: Bearer fc_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "khach@example.com",
    "name": "Nguyễn Văn A",
    "challengeSlug": "ai-agent-challenge-21-day",
    "externalOrderId": "LDP-12345"
  }'

Ví dụ có cấp gói trả phí (Builder, 1 năm):

curl -X POST https://focus.camp/api/integrations/member \
  -H "Authorization: Bearer fc_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "khach@example.com",
    "name": "Nguyễn Văn A",
    "externalOrderId": "LDP-12346",
    "tierKey": "builder",
    "billingPeriod": "yearly"
  }'

Ghi công người giới thiệu (bán ở web ngoài)

Khi khách bấm link giới thiệu trên focus.camp, hệ thống đặt cookie fc_ref. Nhưng cookie đó không đi qua được biên miền, nên đơn thu tiền ở web khác phải tự mang mã về. Đường đi:

link giới thiệu  →  trang bán của bạn ?fc_ref=aB3dEf7h
                        ↓  (giữ mã qua các bước: form, giỏ, thanh toán)
                    khách trả tiền
                        ↓
                    POST /api/integrations/member  { …, "refCode": "aB3dEf7h" }

Nếu trang bán của bạn có nhiều bước (đặt lịch trước, thanh toán sau vài ngày), hãy lưu mã ngay ở bước đầu tiên và đọc lại lúc gửi webhook — query string và sessionStorage đều mất khi khách mở link thanh toán từ email.

curl -X POST https://focus.camp/api/integrations/member \
  -H "Authorization: Bearer fc_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "khach@example.com",
    "name": "Nguyễn Văn A",
    "challengeSlug": "ai-agent-challenge-21-day",
    "externalOrderId": "LDP-12345",
    "refCode": "aB3dEf7h",
    "amountVnd": 3000000
  }'

Mặc định hoa hồng được ghi nhận ngay khi gọi webhook. Muốn chỉ trả khi người học hoàn thành thử thách (và tuỳ chọn: không được nộp trễ), bật ở Cài đặt của challenge → Hoa hồng giới thiệu. Khi đó khoản hoa hồng nằm ở trạng thái chờ điều kiện, tự mở khoá lúc người học hoàn thành, và bị huỷ nếu họ trễ quá mức cho phép hoặc rời thử thách.

Ô khai quy tắc hoa hồng trong cài đặt challenge
Cài đặt challenge → Hoa hồng giới thiệu. Khối cuối cùng hiện đúng thể lệ mà người giới thiệu sẽ đọc thấy — bạn khai gì, họ đọc y như vậy.

Người giới thiệu lấy link ở đâu

Thành viên vào Community → Affiliate để lấy link. Ngoài link chung của cộng đồng, mỗi món có trang bán riêng sẽ có một link riêng trỏ thẳng tới trang đó. Món chưa có trang bán thì dùng chung link cộng đồng — vẫn ghi công, nhưng người bấm vào đáp xuống trang cộng đồng chứ không thấy đúng món.

Danh sách link giới thiệu theo từng món
Mỗi món hiện sẵn số tiền kiếm được, sắp theo giá trị giảm dần. Món chưa có trang bán riêng được nói rõ, kèm lối tạo nhanh cho người quản lý.

Response thành công (200)

{
  "ok": true,
  "externalOrderId": "LDP-12345",
  "userCreated": true,          // false nếu email đã có tài khoản
  "challengeJoined": true,      // false nếu không truyền challengeSlug
  "tierGranted": false,         // true nếu có cấp gói trả phí (tierKey)
  "alreadyProcessed": false,    // true nếu order này đã xử lý trước đó
  "referralAttributed": true,   // true nếu refCode ghi công được cho ai đó
  "commissionRecorded": true    // true nếu đã sinh dòng hoa hồng
}

Gửi refCode mà thấy referralAttributed: false nghĩa là mã không khớp link nào, hoặc người mua đã được ghi công cho người giới thiệu khác từ trước (luật first-touch: người mang khách về đầu tiên giữ công), hoặc họ tự giới thiệu chính mình.

Lỗi

HTTPerrorNguyên nhân / xử lý
400invalid_payloadBody sai schema. Sửa request, đừng retry nguyên trạng.
400challenge_not_foundchallengeSlug không tồn tại trong community của key.
400tier_not_foundtierKey không có trong cấu hình gói của community.
400tier_is_freetierKey trỏ tới gói free (Explorer) — không cần cấp qua đây, bỏ trống tierKey là đủ.
400billing_period_not_availablebillingPeriod không nằm trong các kỳ hạn đang bật của community.
401unauthorizedThiếu / sai key, key đã revoke hoặc hết hạn.
403insufficient_scopeKey chưa tick scope Provision members.
403challenge_not_allowed_for_this_keyKey bị giới hạn theo challenge, còn challengeSlug trỏ tới challenge ngoài danh sách cho phép của key.
409community_inactiveGói community hết hạn — đang read-only. Retry vô ích; gia hạn gói trước.
429rate_limitedQuá 60 request/phút (theo IP và theo key). Đợi theo Retry-After rồi gọi lại.
500internalLỗi hệ thống. Retry cùng externalOrderId là an toàn (idempotent).

Xem thêm: MCP API · Hướng dẫn vận hành community

Website đang trong quá trình xin giấy phép.