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.

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"
  }'

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 đó
}

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