The WisePub API lets your CRM, checkout or automation tool add members, subscribe them to plans, cancel subscriptions and keep member details in sync. This article covers every endpoint available with your site’s API key, the parameters each one accepts, and the responses you can expect.
All endpoints are relative to your site’s address, for example https://yoursite.com/api/subscription/12.
Authentication
The API uses an API key to authenticate access. You may insert the provided API key in your request’s Authorization header like the following:
Authorization: Bearer {API key}
The other option is to add the API key in the key parameter for every request. For the read (GET) endpoints, add it to the query string, for example ?key={API key}.
If the key is missing or does not match, the request is rejected with a 403 response.
Request format: POST endpoints accept form data or JSON. When sending JSON, set the Content-Type: application/json header. True/false parameters accept true, false, 1, 0, yes or no.
Subscribers
New Subscriber
POST /api/subscription/{Subscription ID}
Insert the ID of the subscription you want the user to subscribe to. If the user does not exist, we create it. If the user exists (matched by email), we update it.
Query Parameters
| Parameters | Description |
|---|---|
| key | API key provided. Required if not set in request Authorization header. |
Required. Email of the subscriber. Must include a valid domain extension (for example .com). |
|
| external_id | Optional. External ID of the subscriber, such as the contact ID in your CRM. Can be any text or number. |
| contact_id | Optional. Alternative name for external_id. If both are sent, contact_id is used. |
| source | String identifier of source, such as the name of the system sending the request. Default to ‘api’ if not set when the member is created. |
| first_name | First name of the subscriber. Defaults to “Student” for new members. |
| last_name | Last name of the subscriber. Defaults to “Member” for new members. |
| password | Password of the subscriber. (if empty, a random one will be generated). Only used when the member is created. |
| phone_number | Phone number of the user format should be → 1234567890 |
| live_scanner | True or false (this is only if you use scanner with F2-Tech) |
| welcome_email | True or false. Determines if the welcome email should be sent. Default is true. |
Example:
curl -X POST "https://yoursite.com/api/subscription/12" \
-H "Authorization: Bearer {API key}" \
-d "[email protected]" \
-d "first_name=Jane" \
-d "last_name=Doe" \
-d "external_id=A-10045" \
-d "source=my-crm"
What happens to existing members
- Not subscribed, or subscription expired or inactive: the member is subscribed again and receives the welcome email (unless it is turned off).
- Already has an active subscription to this plan: the subscription is left as is and no welcome email is sent. Their other details (name, phone number, etc.) are still updated.
- Existing members keep their current password. Their welcome email asks them to continue using it.
About the welcome email
The welcome email is sent only when all of the following are true:
- welcome_email is not set to false
- The Disable API welcome email setting is turned off (Settings > Members & Subscriptions)
- The subscription itself does not have its welcome email disabled
Webhooks with customData (GoHighLevel)
If your request body contains a customData object, as GoHighLevel webhooks do, each key inside it is read as if it were a regular parameter. For example, customData.email is used as email.
If the request includes a location object with an id (also sent by GoHighLevel), that location ID is saved as the member’s source.
Subscribe Member To Multiple Subscriptions at once
POST /api/member/subscribe
This endpoint creates or updates a member and subscribes them to one or more subscription plans.
| Parameters | Description |
|---|---|
| key | API key provided. Required if not set in request Authorization header. |
| Required. Email of the subscriber. Must include a valid domain extension. | |
| external_id | Required. External ID of the subscriber. Can be any text or number. |
| source | String identifier of source. Default to ‘api’ if not set when the member is created. |
| first_name | First name of the subscriber. |
| last_name | Last name of the subscriber. |
| password | Password of the subscriber. (if empty, a random one will be generated). Only used when the member is created. |
| phone_number | Phone number of the user format should be → 1234567890 |
| live_scanner | True or false (this is only if you use scanner with F2-Tech) |
| welcome_email | True or false. Determines if welcome email should be sent. Default is true. |
| subs_ids | Array of subscription IDs to subscribe the user to. Every ID must belong to an existing subscription. |
Please understand the subs_ids need to be an array. In a system like Infusionsoft you might need to input it like this:
subs_ids[0] = 1
subs_ids[1] = 2
subs_ids[2] = 3
In JSON, send it as a list: "subs_ids": [1, 2, 3].
Pro Tip: When updating an existing member, always send first_name and last_name. If they are left out, this endpoint resets them to “Student” and “Member”.
If the external_id and source you send already belong to a different member, the request returns a 409 response. See Responses and errors.
Cancel Subscriptions
POST /api/subscription/delete/{Subscription ID}
Insert the ID of the subscription you want the user to unsubscribe from.
| Parameters | Description |
|---|---|
| key | API key provided. Required if not set in request Authorization header. |
| Required. Email of the user to unsubscribe. |
The member’s account is kept. Only their access to this subscription is removed.
Members
Update member data
POST /api/member/update/{external ID}
Use this endpoint to update a member you previously added with an external ID.
The member is identified by the external_id and source sent in the request body, not by the external ID in the address. Always send external_id in the body. Keep the external ID in the address as well so existing integrations continue to work.
All other parameters are optional. For example, you can add only the email if this is the only data that you wish to update.
| Parameters | Description |
|---|---|
| key | API key provided. Required if not set in request Authorization header. |
| external_id | Required. External ID of the user. |
| source | String identifier of source. Must match the source the member was created with. Defaults to ‘api’ if not set. |
| contact_id | Optional. If sent, replaces the member’s external ID with this value. |
| New user email. | |
| first_name | New first name of the user. |
| last_name | New last name of the user. |
| password | New password of the user. |
| phone_number | Phone number of the user format should be → 1234567890 |
| live_scanner | True or false |
Pro Tip: If you get a “User not found” response, check the source. Members created with a custom source (or a GoHighLevel location ID) can only be found when that same source is sent. Leaving it out searches for members with the source “api”.
A few things to know:
- Only users with the Member role can be updated through this endpoint. Staff accounts return “User not found”.
- If the request includes a location object with an id, it is saved as the member’s new source.
- This endpoint updates member details only. It does not change subscriptions.
Update member by email
POST /api/member/update-by-email
Use this endpoint when you know the member’s email address but not their external ID. It is useful for adding an external ID to members who were created without one.
| Parameters | Description |
|---|---|
| key | API key provided. Required if not set in request Authorization header. |
| Required. Email of the member to update. | |
| first_name | New first name of the user. |
| last_name | New last name of the user. |
| external_id | New external ID of the user. |
| contact_id | Optional. Alternative name for external_id. If both are sent, contact_id is used. |
| source | New string identifier of source. |
| password | New password of the user. |
| phone_number | Phone number of the user format should be → 1234567890 |
| live_scanner | True or false |
A few things to know:
- The email is used to find the member, so it cannot be changed here. To change a member’s email, use Update member data above.
- Only users with the Member role can be updated. Other users return “User not found”.
- If the request includes a location object with an id, it is saved as the member’s source.
- This endpoint updates member details only. It does not change subscriptions.
Example:
curl -X POST "https://yoursite.com/api/member/update-by-email" \
-H "Authorization: Bearer {API key}" \
-d "[email protected]" \
-d "external_id=A-10045" \
-d "source=my-crm"
Read endpoints
These endpoints return information from your site. They use the same API key and are called with GET.
Member count
GET /api/user-count
Returns the total number of user accounts on your site, including staff accounts.
{
"data": {
"count": 1250
}
}
List subscriptions
GET /api/subscriptions
Returns every subscription on your site. Use it to look up the subscription IDs needed by the endpoints above.
{
"subscriptions": [
{
"id": 12,
"name": "Premium",
"public_name": "Premium Membership",
"slug": "premium",
"description": "Full access to all rooms",
"order": 1,
"is_public": true,
"redirect_url": null,
"restricted_access_text": null,
"mobile_push_enabled": false
}
]
}
Each subscription also includes a few display settings, such as its theme and logo.
Portfolio
GET /api/portfolios/{Portfolio ID}
Returns one portfolio with its trades. This endpoint is only available when the Portfolio feature is enabled on your site.
| Parameters | Description |
|---|---|
| key | API key provided. Required if not set in request Authorization header. |
{
"success": true,
"portfolio": { "id": 3, "name": "Swing Trades", "slug": "swing-trades", "description": "..." },
"market_data": true,
"fields": [ { "id": 1, "name": "Setup", "type": "text" } ],
"openTrades": [
{
"id": 41,
"symbol": "AAPL",
"entry_date": "2026-09-30T00:00:00.000000Z",
"entry_price": "182.10",
"is_short": false,
"last_value": 185.4,
"pc_change": "0.85",
"pc_change_entry": "1.81",
"fields": { "Setup": "Breakout" }
}
],
"closedTrades": [
{
"id": 38,
"symbol": "MSFT",
"entry_date": "2026-08-12T00:00:00.000000Z",
"exit_date": "2026-09-02T00:00:00.000000Z",
"entry_price": "410.00",
"exit_price": "428.50",
"pc_change": "4.51",
"time_difference": 21,
"fields": { "Setup": "Pullback" }
}
]
}
- fields lists the portfolio’s custom columns. Each trade’s fields object holds its value for each column, keyed by column name.
- last_value, pc_change and pc_change_entry on open trades come from live market data and are empty when it is not available.
- On closed trades, pc_change is the gain or loss in percent and time_difference is the number of days the trade was held.
Calendar events
GET /api/calendar/events
Returns every calendar event on your site, including events that are not linked to a subscription. Results are paginated.
| Parameters | Description |
|---|---|
| key | API key provided. Required if not set in request Authorization header. |
| updated_since | Optional. Date and time (for example 2026-10-01T00:00:00Z). Only returns events changed since then. |
| per_page | Optional. Number of events per page, from 1 to 500. Default is 100. |
| cursor | Optional. The next_cursor value from the previous page, to get the next page. |
{
"data": [
{
"id": 88,
"name": "Morning Market Briefing",
"description": "Daily pre-market review",
"url": null,
"color": "#2563eb",
"start_at": "2026-10-20T13:30:00+00:00",
"end_at": "2026-10-20T14:30:00+00:00",
"recurring": true,
"recurring_rule": "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR",
"recurrence_end_at": null,
"suppressed_dates": [],
"track_attendees": false,
"parent_event_id": null,
"original_start_at": null,
"room": { "id": 5, "name": "Main Room", "slug": "main-room" },
"calendar": { "id": 1, "name": "Live Events" },
"subscriptions": [ { "id": 12, "name": "Premium", "slug": "premium", "sku": "PREM" } ]
}
],
"meta": { "next_cursor": "eyJpZCI6ODgsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0", "per_page": 100 },
"app_name": "Your Site",
"app_url": "https://yoursite.com"
}
When next_cursor is null, you have reached the last page. Deleted events are not returned.
Available chat rooms
GET /api/rooms/available
Returns every room with chat turned on that is not hidden, along with the subscriptions that give access to it. Results are paginated.
| Parameters | Description |
|---|---|
| key | API key provided. Required if not set in request Authorization header. |
| per_page | Optional. Number of rooms per page, from 1 to 500. Default is 100. |
| cursor | Optional. The next_cursor value from the previous page, to get the next page. |
{
"data": [
{
"id": 5,
"name": "Main Room",
"slug": "main-room",
"description": "Live trading room",
"chat_paused": false,
"moderated": true,
"cash_tags": true,
"private_mode": false,
"allow_anonymous": false,
"subscriptions": [ { "id": 12 }, { "id": 14 } ]
}
],
"meta": { "next_cursor": null, "per_page": 100 },
"app_name": "Your Site",
"app_url": "https://yoursite.com"
}
A room is open to all members when its subscriptions list is empty or allow_anonymous is true. Otherwise, a member needs an active subscription to at least one of the listed subscriptions. Members open a room at https://yoursite.com/room/{slug}.
Responses and errors
Every response is JSON. Use the status code to know whether the request worked.
| Status | Meaning |
|---|---|
| 200 | Success. POST endpoints return a message: “User created successfully”, “User updated successfully”, “User subscribed successfully” or “User unsubscribed successfully”. |
| 403 | The API key is missing or incorrect. The response body is empty. |
| 404 | “User not found”: no member matches the email, or the external ID and source you sent. An empty 404 means the subscription or portfolio ID in the address does not exist. |
| 409 | “Duplicate external ID conflict”: see below. |
| 422 | Validation error. A required parameter is missing or a value is not valid. The response lists each problem. |
Success example:
{ "message": "User created successfully" }
Validation error example (422):
{
"message": "The email field must be a valid email address.",
"errors": {
"email": ["The email field must be a valid email address."]
}
}
Duplicate external ID conflict (409)
Each combination of source and external_id can belong to only one member. A 409 response means the request would give a member a source and external ID that another member already has. It usually happens when two contacts in your CRM share an ID, or when a contact’s email changed in your CRM and the old WisePub account still holds the ID.
What happens next depends on the Match API updates on source and external ID setting, found in Settings > Members & Subscriptions under API and access:
- On (default): updates to an existing member are rejected with a 409, and nothing is changed.
- Off: updates to an existing member (New Subscriber, Subscribe Member and Update member by email) go through. The member keeps their current source and external ID, and every other change is saved.
Creating a new member with a source and external ID that are already taken, and the Update member data endpoint, always return a 409.
Pro Tip: If you set an Error report email in Settings > Site, WisePub sends a notification to that address each time a duplicate external ID conflict occurs, with the email, source and external ID involved.
Tips & Best Practices
- Always send the same source for a given system. It is how WisePub tells members from different systems apart.
- Use GET /api/subscriptions to confirm subscription IDs before setting up your integration.
- Set welcome_email to false when importing existing customers who already have access, so they are not emailed again.
- Keep your API key private. Anyone with the key can add, update and unsubscribe members.