Send us a message and we'll get back to you shortly.
SureCart handles the sales side of a course business well: checkout, coupons, and receipts. The courses themselves live in a hosted LMS such as Cubite. A SureCart LMS integration is the missing piece between the two: when someone pays, the LMS should create their account, grant course access to what they bought, and send them a way to sign in, with nobody copying names out of an order screen.
This tutorial builds that connection for a Cubite site with Ottokit, a WordPress automation plugin, plus an appendix for Uncanny Automator. There is no code to write. At the end, a buyer is enrolled in the matching Cubite course in less than a minute and gets a one-click sign-in email; an optional second workflow removes access after a refund.
Conventions used in this guide. Two addresses to keep apart: https://cubite.io/admin is the Cubite admin, where you create keys, find course IDs, and see learners; https://academy.example.com stands in for your Cubite site, where learners go and where every link Cubite sends points (on a Cubite subdomain, use https://your-site.cubite.io instead; nothing else changes). The example course is "Workplace Safety Basics", slug workplace-safety-basics. Replace placeholders in capitals, such as COURSE-ID, with your own values.
An API is a web address that accepts instructions from another program; the API key is a long password proving the instruction came from your store. With those two words in place, the whole integration is five steps:
magicLinkUrl).Cubite receives the buyer's email, name, and course, plus the SureCart order number (echoed back, not stored). Payment stays in SureCart; Cubite charges nothing and does not record the order.
The key lets Ottokit talk to your site.
Fill in the form:
SureCart via Ottokit.A green Key created / Save this secret now tile appears with a Copy button. Copy the key into your password manager, or paste it straight into Ottokit in Step 4 with this tab kept open. It is ck_ followed by 64 letters and digits. Cubite shows it once; if you lose it, revoke it and create a new one.
What you should see: the new key listed under API keys with its name and the enrollments:write scope, and the full ck_ secret saved somewhere private.
Cubite identifies courses by an internal ID, not by name or web address. Fill in one row per course you sell:
| SureCart product | Cubite course ID | Course slug | redirectTo value |
|---|---|---|---|
| Workplace Safety Basics | COURSE-ID | workplace-safety-basics | /course/workplace-safety-basics/courseware |
| (your next course) |
Course ID. Open Courses in the Cubite admin, then the course. The address bar reads https://cubite.io/admin/courses/COURSE-ID; the last part is the ID, a long string of lowercase letters and digits. It looks like clx1abc2d0000abcd1234efgh. Copy it exactly.
Slug and redirectTo. The slug is the last part of the course's public address, https://academy.example.com/course/workplace-safety-basics. It is not the ID; it only builds the redirectTo value, the learning page the buyer lands on after signing in. Join three pieces with no spaces: /course/ + workplace-safety-basics + /courseware = /course/workplace-safety-basics/courseware.
Prerequisites. If a course requires other courses first, a paying buyer who has not completed them is refused (a 409 error): money taken, no enrollment. Either add the row skipPrerequisiteCheck = true in Step 4b, remove the prerequisites, or sell the sequence as a learning path (see "Selling a bundle").
What you should see: one row per product, each with a long lowercase ID (about 25 characters) and a redirectTo value starting with /course/.
Ottokit needs an existing purchase of the product to show you its fields, and its test button really enrolls whoever made that purchase. Make it yourself first, so the sample is your test mailbox and not a real customer.
QA test. Promotion Code (typed at checkout): QA100. Amount: Percentage off, 100. If the coupon screen offers a redemption or usage limit, set a small number. Save.QA100. If checkout still asks for a card at $0, enter it; nothing is charged.What you should see: SureCart's confirmation page, and its receipt in the test mailbox. Nothing happens in Cubite yet, because the workflow does not exist.
Build one workflow per course product. Build and test the first, then copy it for the others, changing the Product in the trigger and the courseId and redirectTo values in the API step.
SureCart to Cubite: Workplace Safety Basics and click Create.SureCart, and choose the trigger Purchase Created. SureCart creates one purchase per product line when a checkout is paid, so this fires once per course bought (test a two-product order once).Customer Email, Customer Name, and Order ID; your exact labels may differ slightly. Note which hold the buyer's email, name, and order ID.What you should see: a saved trigger whose fetched sample shows your test mailbox as the customer email.
API, select the API app.Method: POST. Payload Type: JSON.
Endpoint URL:
https://academy.example.com/api/v1/enrollments
Authentication: API Key. Key name: X-Cubite-Key. Value: the ck_... key from Step 1. The third option asks whether to send the key in the Header or as a Query Parameter: choose Header.
Add Headers: check the box and add one row: key Content-Type, value application/json.
Add Parameters: check the box. Each row becomes one field Cubite receives. Type keys exactly as shown (capitals matter: courseId, not courseid). For dynamic values, click into Value, type @, and pick the SureCart field from 4a.
| Key | Value |
|---|---|
email | @ and the buyer's email field from the SureCart trigger |
name | @ and the buyer's name field |
courseId | the course ID from your Step 2 table |
redirectTo | the redirectTo value from your Step 2 table, for example /course/workplace-safety-basics/courseware |
externalReference | type surecart-, then @ and pick the order ID field (echoed back for matching runs to orders; not stored) |
sendInvite | true |
sendInvite makes Cubite email the buyer the sign-in link itself, so the whole workflow is two steps. Type true as the value and nothing else (Cubite also accepts 1 or yes, and ignores capitals and spaces around it). To write the email in Ottokit instead, leave the row out and see 4c. For a course with prerequisites, add the row skipPrerequisiteCheck = true.
Send Entire Payload: unchecked. Wrap Request in Array: No.
Behind the form, Ottokit sends Cubite a request like this (it sends each parameter as text, so true arrives in quotes; Cubite accepts it either way):
Test and save. Click Continue to reach the Test tab. Test Action sends a real request using the purchase fetched in 4a: it enrolls that buyer and, with sendInvite, emails them a link, which is why the sample must be your test mailbox. Click it, compare the response with this example, then Save.
"status": "completed" refers to the enrollment, not the course: it means full, paid access with no Cubite checkout step. "emailSent": true means Cubite handed the sign-in email to the mail provider; check the test mailbox now. "magicLinkUrl" is the link in the email. "created": true means this call created the enrollment (false: already enrolled, re-confirmed), and "expiresAt" shows a date only when the course has an access period set in the admin.
If "emailSent" is false with the sendInvite row present, check that the value is true and test again; if it still is, check that Member sign-in invite is switched on under Sites -> your site -> Emails. The enrollment worked either way. An "error": line is explained in Troubleshooting.
What you should see: a response with "emailSent": true, and within a minute the sign-in email in your test mailbox.
Path A (recommended): Cubite sends it. With sendInvite set to true there is no email step to build.
redirectTo page.noreply@cubite.io with your site's name as the sender name.emailSent is false; use Path B then.Path B: Ottokit sends it. For your own wording, remove the sendInvite row (Cubite then sends nothing and returns a magicLinkUrl valid for 24 hours) and add an email step after the API step:
Gmail, choose Send Email. Select Connection -> Create Connection, sign in to the Google account you send from, click Allow.@ and the buyer's email field. From Name: your business name. Subject: for example Your Workplace Safety Basics course is ready.SIGN-IN-LINK, type @, search for magicLinkUrl, click it. For the greeting, pick the customer name field the same way (it may be the full name) or write "Hi there".Hi FIRST-NAME, Thank you for your purchase. Your course is ready on the Example Academy site. Click this link to sign in and start (no password needed): SIGN-IN-LINK The link works once and expires 24 hours after this email was sent. If it has expired, go to https://academy.example.com/auth/signin and click "Email me a sign-in link" for a fresh one. Kind regards, The Example Academy team
Ottokit also offers an SMTP Send Email app and a WordPress -> Send Email action if you would rather not send from Gmail; the mapping is similar (To, Subject, Body with the @ picker).
What you should see: on Path A, nothing to build; on Path B, a test email in the test mailbox whose link opens the course learning page.
Click Publish at the top right. The label changes to Update, which means the workflow is live. Until then, real purchases do nothing.
What you should see: the top-right button reads Update, not Publish.
Use a second test address (name+test2@example.com) so you see a brand-new enrollment; reusing the Step 3 address returns "created": false, which is also correct.
QA100.QA100 coupon; recreate it whenever you test again.What you should see: all three places agree on the same enrollment, and the link signs you in without a password.
Spotting a failed run later. Nothing alerts you to a failed run, so after the first few sales, or when a buyer says no link arrived, open the workflow's history in Ottokit (kept 30 days on Business) and look at the API step for an "error": line. The fallback is always manual: Courses -> the course -> Learners -> Add learner.
Tell buyers to expect a second email. SureCart's confirmation and the sign-in link arrive separately, so add a line to the Order Confirmation email (SureCart -> Settings -> Notifications -> Edit): "Your course sign-in link arrives in a separate email within a few minutes; check spam if you do not see it."
Why a live coupon rather than test mode. It is not documented whether Ottokit's triggers fire for SureCart test-mode orders, and with Test Mode Restricted on (Settings -> Advanced -> Spam Protection & Security) a non-admin sees "Test checkout successful" but no order is created.
Refunding in SureCart with Revoke Purchase checked (Orders -> the order -> Charge Section -> three-dot menu -> Refund) marks the purchase revoked; a refund without that box revokes nothing. Customers -> the customer -> Purchases -> Revoke does the same without a refund. Both fire one trigger, which a second workflow passes to Cubite.
Build one refund workflow per course product. A single workflow that fires on any refund but always sends the same courseId would, on a refund of course B, remove access to course A, which the buyer still paid for.
Before you start, refund your Step 5 test order with Revoke Purchase checked, so the trigger has a record to fetch.
SureCart refund to Cubite: Workplace Safety Basics.SureCart -> Purchase Revoked -> your connection. If the trigger offers a Product selector, choose this course's product. Continue -> Fetch Data -> Save. If the fetched data shows a customer number but no buyer email, stop and contact Cubite support (hello@cubite.io).@), Operator is equal to, Value the product's exact name. Continue -> Test Action (it should show True) -> Save.DELETE, and only two parameter rows, courseId = this course's ID and email = @ the buyer's email field.Success looks like:
{ "revoked": true }
404 No enrolment found for that learner (the response spells it that way) means the learner was not enrolled; harmless.
Fallback 1: values in the address. If Test Action returns courseId is required or email or userId is required, Ottokit did not send the parameter rows with a DELETE. Set Endpoint URL to the address below (your course ID in place of COURSE-ID), click after the final =, type @, pick the buyer's email field, and remove the two parameter rows. Success looks the same.
https://academy.example.com/api/v1/enrollments?courseId=COURSE-ID&email=
Fallback 2: end access with PUT. If Ottokit will not place a field inside the URL, change Method to PUT, keep the parameter rows, and add a row expiresAt = 2020-01-01T00:00:00Z. Success starts { "enrollment": .... This sets the access expiry to the past instead of deleting the record: the course shows "Access expired" on the learner's dashboard and the lesson package stops recording progress, but not every page is locked on its own, so contact Cubite support (hello@cubite.io) if you end up here.
What you should see: on the course's Learners tab, with DELETE the test address is gone; with PUT it remains, with a past expiry. Remove from course on a learner's row removes anyone by hand.
If a SureCart product should unlock a Cubite learning path (several courses in sequence), the call changes:
https://academy.example.com/api/v1/learning-paths/LEARNING-PATH-ID/enrollPOSTemail and name only.Cubite creates or finds the learner and enrolls them in the path and every course unlocked at the start, skipping payment and prerequisite checks. A success response (201) contains enrollment, enrolledCourses, and user. The path ID is the last part of the address bar at Learning Paths -> the path.
This endpoint sends no email and returns no link. Two ways to get the buyer signed in:
POST to https://academy.example.com/api/v1/users/invite with email and redirectTo (for example /dashboard). It returns { "issued": true, "magicLinkUrl": "..." }, a 24-hour link with the same eligibility rules, which you email from Ottokit as in Path B. That call needs the users:write scope on your key.| What you see | Likely cause | Fix |
|---|---|---|
401 Missing X-Cubite-Key header | Key not sent as a header, or header name misspelled | Authentication = API Key, Key name exactly X-Cubite-Key, in Header |
401 Invalid API key | Mistyped, an extra space, or another site's key | Re-paste from your password manager; if unsure, revoke and create a new key |
401 API key revoked | The key was revoked in Cubite | Create a new key and paste it into every workflow |
401 API key expired | Its expiration date passed | Edit the expiration under API keys, or create a new key |
403 Key missing required scope: enrollments:write (or another scope) | That scope was not checked on the key | Edit the key and check it: enrollments:write for enroll and refund, learning-paths:write for bundles, users:write for /users/invite |
403 Source IP not allowed for this API key | An IP allowlist is set on the key | Edit the key and clear the allowlist |
400 Invalid JSON body | Payload Type is not JSON, or a hand-typed body has a typo | Set Payload Type to JSON; use the parameter rows |
400 courseId is required, email and courseId are required, or email or userId is required | Misspelled key (capitals matter); empty mapped field; or no parameter rows sent with the DELETE | Check email and courseId spelling; re-run Fetch Data and re-map; on refunds use Fallback 1 or 2 |
400 Invalid email | email is mapped to the wrong SureCart field | Re-map email to the buyer's email field |
404 Course not found on this site | A slug or another site's course was used instead of this site's course ID | Copy the ID from the address bar in Courses |
404 No enrolment found for that learner (refund) | Never enrolled, or the email differs from the enrollment | Harmless if no revoke was expected; otherwise check courseId and the email |
409 Prerequisites not satisfied | The course has prerequisites the buyer has not completed | Remove them, or add the row skipPrerequisiteCheck = true; enroll the buyer by hand meanwhile |
429 Rate limit exceeded for this API key. Retry later. | Over 120 write calls in one minute (only a bulk loop gets there) | Wait a minute and run it again |
200 with "created": false | Already enrolled (for example a repeat purchase); Cubite re-confirmed it and, with sendInvite, emailed a fresh link | Nothing to fix. An existing expiry is not moved; to extend access on a renewal, use PUT with a new expiresAt (Step 6 shows the shape) |
200 with "expiresAt" showing a date | The course has an access period; the enrollment ends then | Nothing to fix |
200 but "magicLinkUrl": null and "emailSent": true | Not eligible for a one-click link (site administrator, account with a password, or account on another Cubite site); Cubite sent the Course enrollment email instead | Nothing to fix; they sign in the normal way. For a test, use a fresh non-admin mailbox |
200 but "emailSent": false with sendInvite present | The value is not true (a typo, or a SureCart field mapped by mistake), or Member sign-in invite is off under Sites -> your site -> Emails | Fix the value; enable the email; or use Path B |
200 with no "emailSent" or "created" line at all | Your site is on an older Cubite release that predates sendInvite | Contact Cubite support; until then use Path B, which needs nothing new |
"emailSent": true but nothing in the inbox | Spam; a typo in the buyer's email; a slow mail provider | Check spam and the email under SureCart -> Customers; the learner can use Email me a sign-in link |
| "This link has expired" or "already been used" | The link is one-time and time-limited | The learner clicks Email me a sign-in link on the sign-in page |
| Learner lands on "My courses" instead of the course | redirectTo was not a path starting with a single / (a full https:// address is ignored) | Use the redirectTo value from your Step 2 table |
| Nothing happens after a real purchase | Workflow not published; wrong Product in the trigger; a test-mode order; or Test Mode Restricted blocked order creation | Check the button says Update; re-check the product; buy on the live store with the coupon |
| Fetch Data shows no purchase | No purchase of that product yet | Make one coupon purchase with the test mailbox (Step 3) and fetch again |
| The buyer mistyped their email | Cubite created the account exactly as typed, so nothing arrived | Correct it under SureCart -> Customers; Add learner with the right address on the course's Learners tab; Remove from course on the wrong one |
ck_ key like a password. Paste it only into Ottokit's (or Automator's) header field: never on a web page, in your theme, in a shared document, or in an email to anyone, including Cubite support.Under Integrations -> Webhooks, Cubite can send signed events such as enrollment.created, course.completed, and certificate.issued to Ottokit's Webhooks -> Catch Webhook trigger (every plan) or Automator's Receive data from a webhook trigger (Pro). The enrollment endpoint also accepts up to 100 learners per request, which suits a one-off import of past buyers; ask Cubite support about that.
The Cubite side is identical; only the WordPress screens change.
Free or Pro? The webhook action is free. Automator's SureCart trigger A user purchases a product is free too, but only runs for buyers logged in to WordPress at the time; A guest purchases a product covers buyers without a WordPress login and needs Automator Pro. SureCart appears to create a WordPress account and sign new buyers in at checkout, so the free trigger may catch most purchases; confirm with one coupon purchase in a private window, and if it does not fire, you need Pro.
The webhook action's Body rows are these (Key / Data type / Value); insert SureCart values with the * button beside each Value field:
| Key | Data type | Value |
|---|---|---|
email | Text | * -> SureCart -> Billing email (or Customer's email, depending on version) |
name | Text | * -> SureCart -> Billing name (or Customer's name) |
courseId | Text | the course ID from your Step 2 table |
redirectTo | Text | the redirectTo value from your Step 2 table |
externalReference | Text | surecart- followed by * -> SureCart -> Order ID |
sendInvite | Boolean | true |
Build the recipe:
SureCart to Cubite: Workplace Safety Basics. Recipe type Logged-in (free trigger) or Everyone (Pro guest trigger).https://academy.example.com/api/v1/enrollments; Request method POST; Data format JSON; Authorization empty (it writes a standard Authorization header, which Cubite does not read); Headers: one row, Name X-Cubite-Key, Value your key.email Value instead of the * SureCart value, click Send test, wait for "Data successfully sent to: ...", then put the * value back before saving.sendInvite row and add a second action below the webhook: Add action -> Emails -> Send an email, Content type HTML, To = * -> SureCart billing email, Subject and Body as in Path B. For the link, click * in the Body and choose Response - magicLinkUrl under the Send data to a webhook group; response tokens appear only after a successful Send test and only for actions placed after the webhook, so run Send test again if it is missing.What you should see: "Data successfully sent to: ..." after Send test, and the same three checks as Step 5 after a coupon purchase.
Automator has no "purchase revoked" trigger. The closest is A refund for a product is issued to a customer (Pro): one recipe per product, with the same webhook action set to Request method DELETE and Body rows courseId and email, or the PUT + expiresAt alternative from Step 6. It fires on every refund, whether or not you checked Revoke Purchase in SureCart, so a partial refund meant to keep access would still remove it in Cubite.