Member Provisioning (sales webhooks)
Sell on any platform — members manage themselves. Connect your sales platform's webhook (Hotmart, Stripe, Dodo Payments, or any platform that signs with Standard Webhooks or sends a secret token, via generic-webhook) and every sale automatically creates a member in your Tenant Auth community: buyers log in with their purchase email (OTP), no password setup, no manual work. Refunds, chargebacks and cancellations revoke access instantly — sessions are killed and gated file downloads stop working on the next click. Map each product/offer to a tenant group, keep a full audit trail of every event, and test the entire chain with the built-in simulator before going live. Optional access for a limited time per product (duration_days, ends automatically). Your own shop works too (provider internal): a paid order gives the buyer access to the gated files of each product they bought, and a refund or cancellation takes exactly those away again. Gated files per product (file_ids) work for every platform. A member who had access before buying keeps it. A refund or cancellation ends only that purchase: a member who still has another active purchase keeps access. After each processed sale or revoke, optional server-side actions run an IAPI chain or move a Flow Framework instance (set-action). Webhook endpoint: POST https://api.websitepublisher.ai/sapi/project/{id}/webhook/{provider}.
Try it with your AI assistant
Once WebsitePublisher is connected to your AI assistant, paste one of these into the chat. Replace the parts in [brackets] with your own details.
@WebsitePublisher connect my Hotmart sales so every buyer of [product] automatically gets member access to [member group]@WebsitePublisher simulate a purchase of [offer code] by [email] and check that the member gets access@WebsitePublisher why can't [email] log in after paying? Check their purchase events and until when their access runsWith WebsitePublisher, connect my Hotmart sales so every buyer of [product] automatically gets member access to [member group]With WebsitePublisher, simulate a purchase of [offer code] by [email] and check that the member gets accessWith WebsitePublisher, why can't [email] log in after paying? Check their purchase events and until when their access runsWith WebsitePublisher, connect my Hotmart sales so every buyer of [product] automatically gets member access to [member group]With WebsitePublisher, simulate a purchase of [offer code] by [email] and check that the member gets accessWith WebsitePublisher, why can't [email] log in after paying? Check their purchase events and until when their access runsEndpoints (13)
Enable a sales platform for this project. Returns the exact webhook_url to paste into the platform's webhook settings, and a setup hint per provider. The shared token or signing secret (Hotmart hottok, Stripe whsec_..., Dodo whsec_...) must be stored in the vault first (vault_store_secret) — pass its key name as secret_ref. Providers: hotmart, stripe (Stripe-Signature, events checkout.session.completed/async_payment_succeeded, invoice.paid grant; customer.subscription.deleted/updated to canceled|unpaid, full charge.refunded, charge.dispute.created revoke), dodo (Standard Webhooks preset), generic-webhook (any platform: you set verify, event_actions and paths in settings). Calling configure again updates status, secret_ref and settings.
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | ✓ Yes | Provider slug, e.g. "hotmart". "internal" = your own checkout: no webhook and no secret; settings {revoke_on_refund, revoke_on_cancel} (both default true) decide whether a refunded or cancelled order loses its access. |
secret_ref |
string | No | Vault key name holding the platform's shared token (e.g. "hotmart_hottok"). |
status |
string | No | active (default) | disabled. |
settings |
object | No | Provider settings; replaces the stored ones, null clears. stripe: {tolerance_secs (30..3600, default 300), metadata_key (default "wp_external_id")}. dodo and generic-webhook: {verify: "standard_webhooks"|"token", token_header, tolerance_secs, event_path (default "type"), event_actions: {"<event type>": "grant"|"revoke"|null}, paths: {email, product, offer, subject, transaction, metadata (an object, carried to actions): dot path or list, e.g. "data.customer.email"}}. dodo has presets for all of these; your entries are merged over them (see effective_settings). |
Link a product or offer at the sales platform to a tenant group. A sale of this external_id then grants membership in tenant_code; refund/cancel revokes it. For Hotmart use the offer code (data.purchase.offer.code) or the numeric product id — both work. Upserts on (provider, external_id). Own checkout (provider internal): external_id is product:<id>, variant:<id> or sku:<sku> (a bare number is a product id; on an order line the variant wins over the sku, the sku over the product); file_ids are the gated files every paid line of it gives the buyer; tenant_code is the tenant of a new member, an existing member keeps theirs. Every provider: file_ids gives the buyer these gated files with each sale; a refund, cancellation or expiry takes them away again unless another active purchase gives the same file. Selling files one by one needs gated-files entitlement_mode asset.
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | ✓ Yes | Provider slug, e.g. "hotmart". |
external_id |
string | ✓ Yes | Offer code or product id at the platform. |
tenant_code |
string | ✓ Yes | Tenant group that this sale grants access to. |
role |
string | No | Optional tenant_auth role for created members (default member). |
duration_days |
integer | No | Optional: access for this many days from each payment (1..3650), then it ends automatically (hourly check). A renewal payment sets the end again; payments do not stack. Omit or null = until refund/cancel (default). |
file_ids |
array | No | Optional, every provider: gated file ids (gated-files list-files) a sale of this offer/product gives the buyer, e.g. [92]; own checkout: per paid order line. Replaces the stored list; [] or null clears it; omit to keep it. |
Remove a product/offer mapping. Sales of that external_id are then audited as "unmapped" and never touch membership.
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | ✓ Yes | Provider slug. |
external_id |
string | ✓ Yes | The mapped offer/product id to remove. |
List all product/offer → tenant_code mappings for this project, optionally filtered by provider.
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | No | Filter by provider slug. |
Audit trail of every received event: processed, duplicate, ignored, unmapped, signature_failed or error — with buyer email, action and detail. Your first stop when a member says "I paid but can't log in".
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | No | Filter by provider. |
status |
string | No | Filter: processed|duplicate|ignored|unmapped|signature_failed|error. |
action |
string | No | Filter: grant|revoke. |
buyer_email |
string | No | Filter by buyer email. |
limit |
integer | No | Page size (default 50, max 200). |
offset |
integer | No | Pagination offset. |
Test the entire chain without the real platform: injects a synthetic grant or revoke event through the same engine (idempotency, mapping, tenant lifecycle, audit). Verify the member appears/disappears and gated downloads open/close — then go live with confidence.
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | ✓ Yes | Provider slug the simulation runs under, e.g. "hotmart". |
action |
string | ✓ Yes | grant | revoke. |
buyer_email |
string | ✓ Yes | The (test) buyer's email. |
external_id |
string | No | Offer/product id — must be mapped via map-offer, else the event audits as "unmapped". |
metadata |
object | No | Optional payment metadata, as a real event carries it (e.g. {"instance_id": "42"} to test a flow_transition action). Simulated events also run the project's actions. |
subject_id |
string | No | Optional subscription/payment id the grant belongs to (e.g. "sub_test1"). A later simulated revoke with the same subject_id ends exactly that grant; one without a matching active grant is audited as ignored. |
Run a server-side action after every processed purchase event: no browser, no polling. event "member.granted" (a verified sale created, reactivated or renewed a member) or "member.revoked" (refund, cancel, dispute or expiry). target_type "iapi_chain": ordered IAPI steps [{service, endpoint, input_template}] with {{fields.*}} from the event (buyer_email, tenant_code, external_id, subject_id, provider, lifecycle, metadata.KEY) and {{steps.N.result...}}, e.g. create a record, render a PDF, unlock gated files, send an email. target_type "flow_transition": moves the Flow Framework instance whose id is in the payment metadata (default metadata.instance_id, e.g. set on the Stripe Checkout Session) with flow.trigger as the internal actor, so an internal-only transition advances only after a verified payment; atomic and idempotent per subscription/payment. Runs once per subscription or payment by default (dedup "subject"; "event" = every received event). Pass action_id to change fields of an existing action. Test with simulate; results in list-deliveries.
| Field | Type | Required | Description |
|---|---|---|---|
action_id |
integer | No | Existing action to update; fields you leave out keep their value. |
event |
string | No | member.granted | member.revoked (required for a new action). |
target_type |
string | No | iapi_chain | flow_transition (required for a new action). |
name |
string | No | Optional label. |
steps |
array | No | iapi_chain: 1..10 steps [{"service": "...", "endpoint": "...", "input_template": {...}}]. A step cannot call member-provisioning itself. |
flow |
object | No | flow_transition: {"trigger": "<transition trigger>", "instance_path": "metadata.instance_id" (default; must start with metadata.), "flow_slug": optional, "from_state": optional (an instance in another state is skipped), "require_email_match": true (default: the instance actor e-mail must equal the buyer e-mail), "data": optional object}. |
filters |
object | No | Optional: {"provider": "stripe", "external_id": "<mapped offer/product>", "lifecycle": [...]}. lifecycle for member.granted: created, reactivated, already_active; for member.revoked: revoked, kept (member keeps access through another purchase), not_active. |
dedup |
string | No | "subject" (default: once per subscription or payment) or "event" (every received event, e.g. every renewal). |
retry |
boolean | No | Retry a failed delivery automatically (max 5 attempts). Default false for iapi_chain (a retry runs every step again), true for flow_transition (idempotent). |
active |
boolean | No | false switches the action off without removing it. |
Remove an action (set-action with active false only switches it off). Its past deliveries stay in list-deliveries.
| Field | Type | Required | Description |
|---|---|---|---|
action_id |
integer | ✓ Yes | The action to remove (from list-actions). |
All server-side actions of this project with their event, target, filters, dedup and effective retry policy.
No input parameters required.
What every action did: per delivery the status (pending, delivered, skipped, failed), attempts, error, result (flow: from and to state) and the event payload it ran with. Your first stop when a chain did not run or a flow did not move.
| Field | Type | Required | Description |
|---|---|---|---|
action_id |
integer | No | Filter by action. |
status |
string | No | Filter: pending|delivered|skipped|failed. |
event |
string | No | Filter: member.granted|member.revoked. |
limit |
integer | No | Page size (default 50, max 200). |
offset |
integer | No | Pagination offset. |
Queue one failed delivery again, e.g. after fixing the chain or the flow. A chain runs all its steps again from the first step.
| Field | Type | Required | Description |
|---|---|---|---|
delivery_id |
string | ✓ Yes | The delivery_id from list-deliveries. |
The grant ledger: one row per paid subscription or payment with buyer email, mapped external_id, tenant_code, status (active, revoked, expired) and expires_at (UTC, when the mapping has duration_days). A member keeps access while at least one grant is active. Use it to answer "until when does this member have access?". Grants of a mapping with file_ids also list files: the gated files that purchase gave and whether that access is still active. Subject member:<id> (existing-member) is the baseline that keeps a member who had access before their first purchase.
| Field | Type | Required | Description |
|---|---|---|---|
provider |
string | No | Filter by provider. |
status |
string | No | Filter: active|revoked|expired. |
buyer_email |
string | No | Filter by buyer email. |
limit |
integer | No | Page size (default 50, max 200). |
offset |
integer | No | Pagination offset. |
Own checkout (provider internal): run one order through member provisioning now. For orders paid before you configured provider internal, to retry a line that failed, or to take an order's access away by hand (action revoke). Idempotent: lines that already gave access stay as they are. dry_run shows per order line which mapping applies and what would happen, without changing anything. Returns per line: status (processed, duplicate, unmapped, ignored, error), detail and the gated files given or taken away.
| Field | Type | Required | Description |
|---|---|---|---|
order_id |
integer | ✓ Yes | Order id (order-management). |
action |
string | No | grant (default) or revoke. |
dry_run |
boolean | No | true = only show what would happen. |
MCP Tool Names
When using this integration through an AI assistant (Claude, ChatGPT, Cursor, etc.), the endpoints are available as MCP tools:
| Endpoint | MCP Tool Name |
|---|---|
| configure | member-provisioning_configure |
| map-offer | member-provisioning_map_offer |
| unmap-offer | member-provisioning_unmap_offer |
| list-mappings | member-provisioning_list_mappings |
| list-events | member-provisioning_list_events |
| simulate | member-provisioning_simulate |
| set-action | member-provisioning_set_action |
| remove-action | member-provisioning_remove_action |
| list-actions | member-provisioning_list_actions |
| list-deliveries | member-provisioning_list_deliveries |
| retry-delivery | member-provisioning_retry_delivery |
| list-grants | member-provisioning_list_grants |
| provision-order | member-provisioning_provision_order |
Website