Support

How can we help?

Send us a message and we'll get back to you shortly.

We typically respond within 24 hours

SureCart

SureCart LMS integration: enroll buyers in your Cubite courses automatically

Amir Tadrisi
Amir Tadrisi
AI for Education Specialist
26 min read
SureCart LMS integration: enroll buyers in your Cubite courses automatically

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.

What you need

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.

How a SureCart LMS integration works

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:

  1. A buyer pays for a course product on your WordPress site (SureCart).
  2. Ottokit sees the purchase and starts a workflow you have built.
  3. Ottokit calls Cubite's enrollment API with the buyer's email, name, and the course ID, using a private key that proves the request came from your store.
  4. Cubite creates the learner (or finds the existing account), enrolls them with full access, and returns a one-click sign-in link (magicLinkUrl).
  5. The buyer gets an email with that link: from Cubite (recommended), or from Ottokit if you want your own wording.

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.

Step 1: Create a Cubite API key

The key lets Ottokit talk to your site.

  1. Sign in at https://cubite.io/auth/signin, then open https://cubite.io/admin.
  2. Click Sites, then your site, then the Integrations tab.
  3. In the Developer group, click API keys, then Create key.

Fill in the form:

  • Name: something you will recognize later, such as SureCart via Ottokit.
  • Scopes: check enrollments:write only ("Enroll learners (single or bulk) and revoke enrollments"); it also covers reading enrollments.
  • Expiration (optional): leave it empty, or set a date with a calendar reminder to rotate the key before then.
  • IP allowlist (optional): leave it empty; Ottokit calls from changing cloud addresses.
  • Click Create key.

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.

Step 2: Write down your course IDs

Cubite identifies courses by an internal ID, not by name or web address. Fill in one row per course you sell:

SureCart productCubite course IDCourse slugredirectTo value
Workplace Safety BasicsCOURSE-IDworkplace-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/.

Step 3: Make one test purchase with a 100% coupon

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.

  1. SureCart -> Promotions -> Coupons -> Add New. Coupon Name (internal): 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.
  2. In a private browser window, buy the course product with your test mailbox and the code 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.

Step 4: Build the SureCart to Cubite workflow in Ottokit

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.

4a. Trigger: a SureCart purchase of this product

  1. OttoKit -> Workflows -> Create Workflow -> Create from Scratch. Name it SureCart to Cubite: Workplace Safety Basics and click Create.
  2. Click plus (+), search for 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).
  3. Under Select Connection, choose your SureCart connection. In the Product selector, choose this course's product so the workflow runs only for it.
  4. Click Continue, then Fetch Data. Ottokit pulls in a recent purchase of that product, normally your Step 3 order. If it shows a real customer, fetch again, pick the newest record, or repeat Step 3; do not use the test button in 4b until the sample shows your test address.
  5. You now see label and value pairs such as Customer Email, Customer Name, and Order ID; your exact labels may differ slightly. Note which hold the buyer's email, name, and order ID.
  6. Click Save.

What you should see: a saved trigger whose fetched sample shows your test mailbox as the customer email.

4b. Action: call Cubite to enroll the buyer

  1. Click plus (+), type API, select the API app.
  2. On the Select tab click API Request, then Continue.
  3. On the Configure tab choose New API and fill in the fields below.

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.

KeyValue
email@ and the buyer's email field from the SureCart trigger
name@ and the buyer's name field
courseIdthe course ID from your Step 2 table
redirectTothe redirectTo value from your Step 2 table, for example /course/workplace-safety-basics/courseware
externalReferencetype surecart-, then @ and pick the order ID field (echoed back for matching runs to orders; not stored)
sendInvitetrue

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.

4c. Choose who sends the welcome email

Path A (recommended): Cubite sends it. With sendInvite set to true there is no email step to build.

  • What the learner receives: your site's Member sign-in invite email: subject "You've been added to Example Academy" (your site's name), one line saying an account was created for them, and a Sign in button.
  • Link lifetime: 7 days. It signs them in with no password and lands on your redirectTo page.
  • Sender: your site's configured sender under Sites -> your site -> Emails. Unless your site is on a paid plan with a verified sending domain, it is sent from noreply@cubite.io with your site's name as the sender name.
  • Wording: fixed, because it carries a sign-in link. Switching it off under Emails -> Member sign-in invite means nothing is sent and emailSent is false; use Path B then.
  • Not everyone gets the one-click link. A site administrator, an account with a password, or an account that also exists on another Cubite site gets the editable Course enrollment email instead ("You're enrolled in Workplace Safety Basics", with a Go to my courses button) and signs in the normal way.

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:

  1. Click plus (+), search for Gmail, choose Send Email. Select Connection -> Create Connection, sign in to the Google account you send from, click Allow.
  2. To: @ and the buyer's email field. From Name: your business name. Subject: for example Your Workplace Safety Basics course is ready.
  3. Body: paste the template below, delete 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".
  4. Continue, Test Action (a real email goes to the test buyer), Save.

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.

4d. Publish

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.

Step 5: Test the finished workflow end to end

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.

  1. In a private window, buy the course product with the second address and the code QA100.
  2. Wait a minute or two (allow up to five), then check the next three places.
  3. Ottokit: the workflow's history shows the trigger, then the API step with a response like the one in 4b.
  4. Cubite admin: https://cubite.io/admin/courses -> the course -> Learners tab lists the test address with Status "Not started" and Access "No expiry" or an expiry date.
  5. Test mailbox: Cubite's "You've been added to ..." email (Path A) or your Ottokit email (Path B). Check spam too.
  6. Click the sign-in link. You should land signed in, on the course learning page, with no password asked.
  7. Delete or disable the 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.

Step 6 (optional): Remove course access after a SureCart refund

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.

  1. Create Workflow -> Create from Scratch, named SureCart refund to Cubite: Workplace Safety Basics.
  2. plus (+) -> 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).
  3. If there was no Product selector, add plus (+) -> Filter: Condition the product name field (via @), Operator is equal to, Value the product's exact name. Continue -> Test Action (it should show True) -> Save.
  4. plus (+) -> API -> API Request -> Continue -> New API, set up exactly as in 4b except: Method DELETE, and only two parameter rows, courseId = this course's ID and email = @ the buyer's email field.
  5. Continue -> Test Action -> Save -> Publish.

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.

Selling a bundle (learning path) instead of one course

If a SureCart product should unlock a Cubite learning path (several courses in sequence), the call changes:

  • Endpoint URL: https://academy.example.com/api/v1/learning-paths/LEARNING-PATH-ID/enroll
  • Method: POST
  • Parameters: email and name only.
  • Scope on the key: learning-paths:write. Edit your key to add it.

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:

  • Tell the buyer to use Email me a sign-in link at https://academy.example.com/auth/signin.
  • Add a second API step: 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.

How learners sign in later

  • The welcome link is one-time and time-limited: 7 days from Cubite (Path A), 24 hours from Ottokit (Path B). A double click, second tab, or email scanner within about 5 minutes is tolerated; after that the page says "This link has already been used. Please request a new one." or, for an old link, "This link has expired. Please request a new one."
  • Email me a sign-in link always works. It is the main button on https://academy.example.com/auth/signin: the learner enters their email, passes the small "I am human" check, and gets a fresh link. API-created accounts have no password, so this is their intended way in.
  • A password is optional. Forgot your password? on the sign-in page sets one. From then on Cubite stops issuing one-click links for that address; later purchases still enroll them, but the email is the Course enrollment one.
  • Each new link for the same email cancels the previous one.

Troubleshooting SureCart to Cubite enrollment errors

What you seeLikely causeFix
401 Missing X-Cubite-Key headerKey not sent as a header, or header name misspelledAuthentication = API Key, Key name exactly X-Cubite-Key, in Header
401 Invalid API keyMistyped, an extra space, or another site's keyRe-paste from your password manager; if unsure, revoke and create a new key
401 API key revokedThe key was revoked in CubiteCreate a new key and paste it into every workflow
401 API key expiredIts expiration date passedEdit 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 keyEdit 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 keyAn IP allowlist is set on the keyEdit the key and clear the allowlist
400 Invalid JSON bodyPayload Type is not JSON, or a hand-typed body has a typoSet Payload Type to JSON; use the parameter rows
400 courseId is required, email and courseId are required, or email or userId is requiredMisspelled key (capitals matter); empty mapped field; or no parameter rows sent with the DELETECheck email and courseId spelling; re-run Fetch Data and re-map; on refunds use Fallback 1 or 2
400 Invalid emailemail is mapped to the wrong SureCart fieldRe-map email to the buyer's email field
404 Course not found on this siteA slug or another site's course was used instead of this site's course IDCopy the ID from the address bar in Courses
404 No enrolment found for that learner (refund)Never enrolled, or the email differs from the enrollmentHarmless if no revoke was expected; otherwise check courseId and the email
409 Prerequisites not satisfiedThe course has prerequisites the buyer has not completedRemove 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": falseAlready enrolled (for example a repeat purchase); Cubite re-confirmed it and, with sendInvite, emailed a fresh linkNothing 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 dateThe course has an access period; the enrollment ends thenNothing to fix
200 but "magicLinkUrl": null and "emailSent": trueNot eligible for a one-click link (site administrator, account with a password, or account on another Cubite site); Cubite sent the Course enrollment email insteadNothing to fix; they sign in the normal way. For a test, use a fresh non-admin mailbox
200 but "emailSent": false with sendInvite presentThe value is not true (a typo, or a SureCart field mapped by mistake), or Member sign-in invite is off under Sites -> your site -> EmailsFix the value; enable the email; or use Path B
200 with no "emailSent" or "created" line at allYour site is on an older Cubite release that predates sendInviteContact Cubite support; until then use Path B, which needs nothing new
"emailSent": true but nothing in the inboxSpam; a typo in the buyer's email; a slow mail providerCheck 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-limitedThe learner clicks Email me a sign-in link on the sign-in page
Learner lands on "My courses" instead of the courseredirectTo 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 purchaseWorkflow not published; wrong Product in the trigger; a test-mode order; or Test Mode Restricted blocked order creationCheck the button says Update; re-check the product; buy on the live store with the coupon
Fetch Data shows no purchaseNo purchase of that product yetMake one coupon purchase with the test mailbox (Step 3) and fetch again
The buyer mistyped their emailCubite created the account exactly as typed, so nothing arrivedCorrect it under SureCart -> Customers; Add learner with the right address on the course's Learners tab; Remove from course on the wrong one

Keeping your Cubite API key safe

  • Treat the 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.
  • If it leaks: revoke it first under Sites -> your site -> Integrations -> API keys, then create a new one and paste it into every workflow (each course and refund workflow holds its own copy).
  • Planned yearly rotation: the opposite order, so no sale fails: create the new key, paste it into every workflow, test one with a coupon purchase, then revoke the old key.

What else you can automate between SureCart and Cubite

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.

Appendix: the same integration with Uncanny Automator

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.

Enroll on purchase

The webhook action's Body rows are these (Key / Data type / Value); insert SureCart values with the * button beside each Value field:

KeyData typeValue
emailText* -> SureCart -> Billing email (or Customer's email, depending on version)
nameText* -> SureCart -> Billing name (or Customer's name)
courseIdTextthe course ID from your Step 2 table
redirectToTextthe redirectTo value from your Step 2 table
externalReferenceTextsurecart- followed by * -> SureCart -> Order ID
sendInviteBooleantrue

Build the recipe:

  1. Uncanny Automator -> New Recipe, titled SureCart to Cubite: Workplace Safety Basics. Recipe type Logged-in (free trigger) or Everyone (Pro guest trigger).
  2. Trigger: integration SureCart, then A user purchases a product (or A guest purchases a product). In the Product dropdown pick the course product, not Any product. Save the trigger.
  3. Add action -> Webhooks -> Send data to a webhook. URL 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.
  4. Add the Body rows from the table above.
  5. Check data format previews the JSON.
  6. Send test sends a real request with whatever is in the fields. Temporarily type your test mailbox in the email Value instead of the * SureCart value, click Send test, wait for "Data successfully sent to: ...", then put the * value back before saving.
  7. Save the action.
  8. To have Automator send the email instead, remove the 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.
  9. Switch the trigger, the actions, and the recipe from Draft to Live, then test with a coupon purchase as in Step 5. Uncanny Automator -> Logs shows each run's response.

What you should see: "Data successfully sent to: ..." after Send test, and the same three checks as Step 5 after a coupon purchase.

Refunds

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.