# LearnWorlds

- Tool: Bubble
- Difficulty: Beginner
- Time required: 2–3 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to LearnWorlds using two required headers — Lw-Client (school subdomain) and Authorization: Bearer (API key) — in Bubble's API Connector. No OAuth, no token expiry. With a LearnWorlds Pro plan and Bubble Starter plan, you can build fully branded course portals, enroll students programmatically, and create public certificate verification pages that give your learners a shareable, professional credential showcase.

## Build a Fully Branded Learning Portal on Top of LearnWorlds

LearnWorlds is built for white-label branding — it's designed to disappear behind your brand's visual identity. Bubble takes that further: instead of using LearnWorlds' own portal template, you build the entire front-end in Bubble. LearnWorlds handles course delivery, interactive video, and certificates. Bubble handles navigation, branding, student dashboards, and custom flows your LearnWorlds portal can't replicate.

The auth setup is beginner-friendly: two static headers, no OAuth flow, no token expiry. Your API key goes in the Authorization: Bearer header (marked Private in Bubble), and your school slug goes in the Lw-Client header. The one distinction from Thinkific's similar pattern is that LearnWorlds' base URL is school-specific — https://YOUR_SUBDOMAIN.learnworlds.com/api/v2 — not a centralized API host. This matters in Bubble because the API Connector base URL must match your exact school URL.

The standout capability of this integration is the certificate verification portal. LearnWorlds generates completion certificates that learners can share via a certificate ID. Bubble can call /api/v2/certificates/{id} from a Backend Workflow (server-side, API key protected) and display the certificate details on a public Bubble page at /verify/[certificateId] — giving learners a shareable, beautifully branded credential page that no standard LearnWorlds portal provides.

Plan requirements: LearnWorlds API access requires the Pro plan or higher (approximately $249/mo on annual billing — verify current pricing at learnworlds.com). Bubble Backend Workflows require Starter plan ($32/mo) or higher.

## Before you start

- A LearnWorlds account on the Pro plan or higher — the Starter plan does not include API access; the API section does not appear in School Settings → Integrations on lower plans
- Your LearnWorlds API key: School Settings → Integrations → API (Pro plan required) → generate and copy the key
- Your LearnWorlds school subdomain: visible in your admin URL as mycompany.learnworlds.com → subdomain is 'mycompany'
- A Bubble account — Starter plan ($32/mo) recommended for Backend Workflows; Free plan works for read-only API Connector calls
- The API Connector plugin installed in your Bubble app (by Bubble, free)

## Step-by-step guide

### 1. Step 1 — Generate your LearnWorlds API key and identify your school subdomain

Before opening Bubble, gather the two values the API Connector needs: your API key and your school subdomain.

In your LearnWorlds admin, click on School Settings in the left sidebar. Under Settings, look for the Integrations section. Click API. If you don't see the API section under Integrations at all, your school is on the Starter plan or lower — API access requires the Pro plan. Contact LearnWorlds support or upgrade your plan before continuing.

In the API section, click Generate Key (or view an existing key if one has already been generated). Copy the key and save it in a secure location — a password manager is ideal. LearnWorlds API keys are static and do not expire on a schedule, but you should treat them with the same security as a password.

Now identify your school subdomain. Look at your browser's address bar while you're in the LearnWorlds admin panel. The URL format is: mycompany.learnworlds.com/admin — the part before .learnworlds.com is your subdomain. For example, if your admin URL is training-hub.learnworlds.com/admin, your subdomain is training-hub.

Critical distinction to memorize before the next step: the Lw-Client header takes ONLY the subdomain value (e.g., training-hub), never the full URL (training-hub.learnworlds.com). This is the most common configuration mistake — it causes a 401 error with no clear indication of which header is wrong.

**Expected result:** You have your LearnWorlds API key (a long string) and your school subdomain (just the short slug, e.g., 'mycompany') ready to configure in Bubble.

### 2. Step 2 — Add the API Connector plugin and configure the LearnWorlds API Group

In your Bubble editor, open the Plugins tab from the left sidebar. Click Add plugins. In the search box type API Connector, then click Install on the 'API Connector by Bubble' result. Close the marketplace overlay after installation.

Click on API Connector in the left Plugins panel. Click Add another API. A new API Group configuration panel opens.

Configure the API Group as follows:

Name: LearnWorlds

Authentication: set to None — LearnWorlds authentication is header-based, not a standard OAuth or Basic auth scheme that Bubble's built-in auth types cover.

Shared headers — click Add a shared header twice:
- Header 1: Key = Lw-Client, Value = your school subdomain (just the slug: e.g., mycompany). Do NOT check Private — this is not a secret.
- Header 2: Key = Authorization, Value = Bearer YOUR_API_KEY_HERE (include the word 'Bearer' followed by a space, then the key). Check the Private checkbox for this header. The Private flag is essential — it tells Bubble to keep this value on Bubble's servers and never send it to the browser.

Shared URL parameters: none needed at the group level.

Base URL (at the Group level): set this to https://YOUR_SUBDOMAIN.learnworlds.com/api/v2 — replace YOUR_SUBDOMAIN with your actual subdomain. This is different from Thinkific, which uses a centralized api.thinkific.com host; LearnWorlds routes every API call through your school's own subdomain.

If you are building a multi-tenant Bubble app that needs to connect to multiple LearnWorlds schools, you can leave the base URL blank here and specify it per-call, or create separate API Groups for each school.

```
{
  "api_group_name": "LearnWorlds",
  "authentication": "None",
  "base_url": "https://YOUR_SUBDOMAIN.learnworlds.com/api/v2",
  "shared_headers": [
    {
      "key": "Lw-Client",
      "value": "mycompany",
      "private": false
    },
    {
      "key": "Authorization",
      "value": "Bearer YOUR_API_KEY_HERE",
      "private": true
    }
  ]
}
```

**Expected result:** The LearnWorlds API Group appears in the API Connector with the school-specific base URL and both required headers. The Authorization row shows a lock icon confirming the Private flag is active.

### 3. Step 3 — Add the Get Courses call and run the Initialize call

Inside the LearnWorlds API Group, click Add another call.

Configure the call:
- Call name: GetCourses
- Method: GET
- URL: /courses (Bubble appends this to the base URL you set at the group level, giving https://YOUR_SUBDOMAIN.learnworlds.com/api/v2/courses)
- Use as: Data (not Action) — this enables binding to Repeating Groups and other data-display elements
- URL parameters: click Add parameter and add:
  - page: default value 1
  - items_per_page: default value 20 (LearnWorlds' API uses items_per_page, not limit)

The Initialize call is mandatory before you can use this call in Bubble workflows or bind it to UI elements. Bubble must see a real, successful API response to auto-detect the schema (field names and data types).

Click Initialize call. Bubble sends the actual GET /courses request to LearnWorlds using your configured headers and base URL. A successful response returns a JSON object with a data array of course objects.

Bubble automatically detects fields from the response. Look for the data array expanding to show fields like id, title, description, created_at, status, image_url, and price. Click Save to lock in the detected schema.

LearnWorlds timestamps: the created_at and updated_at fields come back as Unix epoch integers (seconds since January 1, 1970), not ISO 8601 strings. In Bubble, you cannot directly apply :formatted as date to these raw integers. The conversion: multiply the integer by 1000 (to get milliseconds), then Bubble can read it as a date. You will handle this in Step 5 when building the UI.

If the Initialize call fails with 'There was an issue setting up your call' or returns a 401, the most likely causes are: (1) the Lw-Client header value includes the full domain instead of just the subdomain, (2) the Authorization header is missing the 'Bearer ' prefix, or (3) the base URL subdomain doesn't match the Lw-Client value.

```
{
  "call_name": "GetCourses",
  "method": "GET",
  "url": "/courses",
  "use_as": "Data",
  "url_parameters": [
    { "key": "page", "value": "1" },
    { "key": "items_per_page", "value": "20" }
  ],
  "sample_response": {
    "data": [
      {
        "id": "COURSE_ID_ABC123",
        "title": "Social Media Marketing Masterclass",
        "description": "Learn to build an audience from scratch.",
        "status": "published",
        "price": 199,
        "created_at": 1700000000,
        "image_url": "https://cdn.learnworlds.com/..."
      }
    ],
    "meta": {
      "totalRecords": 8,
      "page": 1,
      "count": 8,
      "totalPages": 1
    }
  }
}
```

**Expected result:** The GetCourses call shows 'Initialized' status. Bubble has detected fields including id (text), title (text), price (number), and created_at (number — the Unix timestamp). The call is ready to bind to a Repeating Group.

### 4. Step 4 — Add API calls for users, enrollments, and certificates

Add three more calls inside the LearnWorlds API Group for the other primary resources you'll use:

GetUsers call:
- Call name: GetUsers
- Method: GET
- URL: /users
- Use as: Data
- URL parameters: page (1), items_per_page (20)
- Key detected fields: id (text, LearnWorlds internal user ID), email, username, created_at (Unix timestamp), tags

GetEnrollments call:
- Call name: GetEnrollments
- Method: GET
- URL: /enrollments
- Use as: Data
- URL parameters: page (1), items_per_page (20)
- Key detected fields: id, user_id, course_id, percentage_completed, completed_at (Unix timestamp or null), started_at (Unix timestamp)
- Note: use user_id to link back to the Users endpoint; you cannot get the student's email directly from the enrollment object without a separate Users call

GetCertificate call (critical for the verification portal use case):
- Call name: GetCertificate
- Method: GET
- URL: /certificates/[certificate_id]
- Use as: Data
- Add [certificate_id] as a dynamic path parameter (Bubble treats bracketed text in URLs as dynamic variables)
- Key detected fields: id, user_id, course_id, issued_at (Unix timestamp), user_name, course_title

Run the Initialize call on each new API call with a real request. For GetCertificate, you will need an actual certificate ID from a completed LearnWorlds course to initialize — complete a test course in LearnWorlds, download the certificate, and find its ID in the certificate URL or via the LearnWorlds admin → Users → [student] → Certificates tab.

Storing LearnWorlds user IDs in Bubble: when a Bubble user logs in for the first time, capture their LearnWorlds user ID via GET /users?email=[email] and store it in a private field on their Bubble User record (mark the field as Private in Bubble's Data tab → Privacy). This stored ID enables one-click enrollment from Bubble admin panels without needing to look up the ID on every operation.

```
{
  "additional_calls": [
    {
      "name": "GetUsers",
      "method": "GET",
      "url": "/users",
      "use_as": "Data",
      "params": { "page": "1", "items_per_page": "20" }
    },
    {
      "name": "GetEnrollments",
      "method": "GET",
      "url": "/enrollments",
      "use_as": "Data",
      "params": { "page": "1", "items_per_page": "20" }
    },
    {
      "name": "GetCertificate",
      "method": "GET",
      "url": "/certificates/[certificate_id]",
      "use_as": "Data",
      "dynamic_params": ["certificate_id"]
    }
  ]
}
```

**Expected result:** Four initialized calls in the LearnWorlds group: GetCourses, GetUsers, GetEnrollments, and GetCertificate. Each shows detected fields, including Unix timestamp fields identified as number type by Bubble.

### 5. Step 5 — Build the branded course catalog and handle Unix timestamp dates

Open a page in the Bubble editor for your course catalog. Add a Repeating Group element.

Repeating Group configuration:
- Type of content: LearnWorlds GetCourses's data (Bubble created this type when you ran the Initialize call)
- Data source: Get data from external API → LearnWorlds - GetCourses
- Pass page: 1 and items_per_page: 20 as parameters
- Layout: choose rows or a grid depending on your card design

Inside the Repeating Group, add these elements for each course:

Course title (Text): Current cell's data's title

Course price: LearnWorlds prices are NOT in cents like Thinkific — they are already in the currency unit (e.g., 199 = $199). Display directly without dividing. Add a condition: when price = 0, show 'Free'.

Course date (Unix timestamp conversion): This is the trickiest Bubble + LearnWorlds gotcha. The created_at field is a Unix timestamp in seconds (e.g., 1700000000). Bubble expects milliseconds for date conversion. In the Text element for course date:
- Set the expression to: Current cell's data's created_at * 1000
- Apply :formatted as date to the result
- Choose your display format (e.g., MMMM D, YYYY → November 14, 2023)

If Bubble doesn't let you do arithmetic directly on the number field in a Text element, use a Calculate expression or a temporary custom state: on page load, set a custom state to the timestamp * 1000, then bind the date element to that state.

Pagination: add a Load More button below the Repeating Group. Create a page-level custom state called current_page (Number, default: 1). When Load More is clicked, set current_page = current_page + 1. In the Repeating Group data source, change the page parameter to current_page state.

Hide the Load More button when on the last page: add a Condition to the button — 'This element is visible' = when meta.totalPages > current_page.

Status filter: LearnWorlds courses have a status field ('published', 'draft', 'hidden'). Add a filter to your Repeating Group's data source or use Bubble's client-side 'Filter' option to show only published courses to students: filter where data's status = 'published'.

**Expected result:** Your course catalog Repeating Group displays published courses from LearnWorlds with readable titles, correct prices (no conversion needed — already in full currency units), formatted dates, and a working pagination system.

### 6. Step 6 — Build the certificate verification portal and enrollment Backend Workflow

This step covers LearnWorlds' two most differentiated features in the Bubble integration context: public certificate verification and server-side enrollment.

Certificate Verification Portal:
Create a new Bubble page named 'verify'. In the page URL settings, add a URL parameter called certificateId (type: text).

Note: Backend Workflows are required to keep the API key server-side for this public page (no logged-in user, no Bubble session). Backend Workflows require Bubble's Starter plan ($32/mo) or higher.

Go to Backend Workflows → Add new API workflow → name it: FetchCertificate.
- Expose as public endpoint: yes
- Parameter: certificate_id (text type)
- Step 1: Plugins → LearnWorlds - GetCertificate, with certificate_id = Step's certificate_id parameter
- Step 2: Make changes to a Result object or Return data — return user_name, course_title, issued_at from step 1's response

On the 'verify' page, add an 'On page load' trigger in the page workflow: Schedule API workflow → FetchCertificate → pass URL parameter 'certificateId' as the certificate_id.

Store the result in custom page states: certificate_holder_name, certificate_course, certificate_date (Unix timestamp × 1000 for date conversion).

Display the certificate details in Text elements bound to the custom states. Add a 'Certificate Verified' badge (show only when certificate_holder_name is not empty). Add an 'Invalid Certificate' message (show when certificate_holder_name is empty).

Enrollment Backend Workflow:
In Backend Workflows, add another new API workflow: EnrollUser.
- Parameters: learnworlds_user_id (text), course_id (text)
- Step 1: Add a CreateEnrollment API call in the API Connector first:
  - Call name: CreateEnrollment
  - Method: POST
  - URL: /enrollments
  - Use as: Action
  - Body (JSON): { "user_id": "<learnworlds_user_id>", "course_id": "<course_id>" }
- Step 2 in workflow: call CreateEnrollment with the workflow's parameter values
- Step 3: Create a Bubble Thing → Enrollment data type → record the enrollment locally

Privacy rules for Enrollment data type (mandatory): Data tab → Privacy → click Enrollment → Add a rule → condition: Creator is Current User → this rule ensures students can only read their own enrollment records, not all students' records across your school.

RapidDev's team has built Bubble apps with complex integrations like certificate portals and multi-step enrollment pipelines — if your use case involves payment gating before LearnWorlds enrollment or SSO between Bubble and LearnWorlds, book a free scoping call at rapidevelopers.com/contact.

```
{
  "create_enrollment_call": {
    "name": "CreateEnrollment",
    "method": "POST",
    "url": "/enrollments",
    "use_as": "Action",
    "content_type": "application/json",
    "body": {
      "user_id": "<learnworlds_user_id>",
      "course_id": "<course_id>"
    }
  },
  "backend_workflow_enrollment": {
    "name": "EnrollUser",
    "parameters": [
      { "name": "learnworlds_user_id", "type": "text" },
      { "name": "course_id", "type": "text" }
    ],
    "steps": [
      "Call CreateEnrollment with workflow parameters",
      "Create Bubble Enrollment Thing (user_email, course_id, enrolled_at = current date/time)"
    ]
  }
}
```

**Expected result:** Visiting /verify/[any-certificate-id] displays the certificate holder's name, course, and completion date if the ID is valid, or an 'Invalid Certificate' message if not. The enrollment Backend Workflow successfully creates enrollments in LearnWorlds when triggered from an admin panel.

## Best practices

- Always mark the Authorization header as Private in Bubble's API Connector — this ensures the Bearer API key never reaches the user's browser, protecting your LearnWorlds school's full administrative access from exposure
- Store your school subdomain in a Bubble App Setting (Settings → App Settings → create 'lw_subdomain') and reference it in both the base URL and Lw-Client header — this makes the configuration auditable and easier to update if your school's subdomain changes
- Set privacy rules on every Bubble Data Type that stores enrollment or certificate data: Data tab → Privacy → restrict each record so that only the user whose email matches the record can read it — without this rule, all enrolled students can read all other students' data
- Convert LearnWorlds Unix timestamps by multiplying by 1000 (to get milliseconds) before applying Bubble's :formatted as date operator — never display raw Unix integers in student-facing UI elements
- Store LearnWorlds user IDs in a private field on Bubble's User data type at first login — fetching the LearnWorlds user ID on every enrollment or course access call is wasteful; cache it once and reuse it
- For certificate verification pages, enable 'Ignore privacy rules when running via Workflow API' on the FetchCertificate Backend Workflow — this is required for unauthenticated public pages; scope this permission to API-only workflows and not to data-writing workflows
- Use Bubble's Workload Unit awareness for pagination — fetching all enrollments across all pages in a single page load burns WU unnecessarily; load page 1 on arrival and trigger subsequent pages only when the user requests them via Load More
- Never put the LearnWorlds API key in a client-side Text element, App Setting that is visible to all users, or Bubble Option Set — even though the Private header flag protects it in API calls, exposing it anywhere else creates a security risk for your entire school's admin access

## Use cases

### White-Label Student Learning Dashboard

Build a fully branded Bubble portal where students log in, see their enrolled courses, track progress via percentage_completed indicators, and click into course player links — all without seeing LearnWorlds' UI. Combine Bubble's User authentication with LearnWorlds user ID mapping stored in a private Bubble field for seamless session continuity.

Prompt example:

```
Show a Repeating Group of all LearnWorlds enrollments for the current user, displaying course title, a progress bar from percentage_completed, and a 'Continue Learning' button linking to the course player URL.
```

### Public Certificate Verification Portal

Create a Bubble page at /verify/[certificateId] that anyone can visit (no login required) to verify a student's completion certificate. A Backend Workflow calls /api/v2/certificates/{id} server-side using the URL parameter, returns the certificate holder's name, course title, and issue date, and Bubble renders a branded verification page. Learners share this URL on LinkedIn or their resume.

Prompt example:

```
When the page loads, extract the certificateId from the URL parameter, trigger a Backend Workflow that calls GET /api/v2/certificates/{id} in LearnWorlds, and display the student name, course name, completion date, and a 'Certificate Verified' badge on the page.
```

### Corporate Training Enrollment Manager

Build an admin panel in Bubble where an HR manager can select employees from a dropdown (populated from Bubble's User table), select a LearnWorlds course from a dynamically fetched course list, and click 'Enroll'. A Backend Workflow fires POST /api/v2/enrollments with the employee's LearnWorlds user_id and the selected course_id, completing enrollment in seconds without logging into LearnWorlds.

Prompt example:

```
Create a Backend Workflow 'EnrollEmployee' that accepts user_id and course_id parameters, POSTs to LearnWorlds /api/v2/enrollments, and updates the employee's Bubble record with an enrolled_at timestamp.
```

## Troubleshooting

### Initialize call returns 401 Unauthorized even after entering correct credentials

Cause: The most common cause is the Lw-Client header value containing the full school URL (e.g., mycompany.learnworlds.com) instead of just the subdomain slug (mycompany). The second most common cause is the Authorization header missing the 'Bearer ' prefix (must be 'Bearer YOUR_KEY', not just 'YOUR_KEY').

Solution: Open the LearnWorlds API Group in the API Connector. Check the Lw-Client header — the value must be only the subdomain slug (e.g., mycompany), not the full URL. Check the Authorization header — the value must start with 'Bearer ' (with a space) followed by the API key. Correct both values and re-run the Initialize call.

### API section does not appear under School Settings → Integrations in LearnWorlds

Cause: The LearnWorlds school is on the Starter plan or lower. API access is a Pro plan feature and above.

Solution: Upgrade the LearnWorlds school to the Pro plan. Check current pricing at learnworlds.com/pricing. As a temporary alternative for non-API access, LearnWorlds admin allows CSV export of enrollments and student data — import that CSV into a Bubble Data Type to display static snapshots of the data without API calls.

### Initialize call shows 'There was an issue setting up your call' with no HTTP status

Cause: The base URL in the Bubble API Group is incorrect — either the subdomain doesn't match, or the URL format is wrong (missing /api/v2 path, or including trailing slash). Alternatively, the LearnWorlds school subdomain in the base URL doesn't match the Lw-Client header value.

Solution: Verify the base URL in the API Group is exactly: https://YOUR_SUBDOMAIN.learnworlds.com/api/v2 — with no trailing slash. Confirm the subdomain in the base URL matches the value in the Lw-Client header exactly. The subdomain is case-sensitive.

### Dates display as large numbers (e.g., 1700000000) instead of readable dates

Cause: LearnWorlds returns timestamps as Unix epoch integers in seconds. Bubble's :formatted as date operator expects milliseconds. Applying :formatted as date directly to the raw integer shows an incorrect very early date or the raw number.

Solution: Multiply the timestamp field by 1000 before applying date formatting. In Bubble's expression builder: set value to [field_name] * 1000, then apply :formatted as date. For display in Text elements, use a Calculate expression or custom state to hold the converted millisecond value.

### Certificate verification Backend Workflow fails with a permissions error when called from a public page

Cause: Bubble's Backend Workflows run with privacy rules applied by default. When called from a public page with no logged-in user, privacy rules that reference 'Current User' fail because there is no current user in the session.

Solution: In the Backend Workflow settings, enable 'Ignore privacy rules when running via Workflow API'. This allows the workflow to fetch API data server-side without requiring an active Bubble user session. Only enable this on workflows that exclusively call external APIs — not workflows that create or modify Bubble Things with user data.

### Backend Workflows option is not visible in the Bubble editor

Cause: The Bubble app is on the Free plan. Backend Workflows are only available on Starter plan ($32/mo) and higher.

Solution: Upgrade to Bubble Starter plan to unlock Backend Workflows. For read-only course display, the Free plan and API Connector with Private headers work fine. For certificate verification (requires Backend Workflow on public page) and enrollment management, Starter plan is required.

### Enrollment POST fails with a 404 or 'user not found' error

Cause: The user_id passed to POST /enrollments is a Bubble internal user ID, not a LearnWorlds internal user ID. These are different ID spaces. Passing Bubble's user ID to LearnWorlds results in a 'not found' error because the user doesn't exist in LearnWorlds' system with that ID.

Solution: Before enrolling, look up the user in LearnWorlds by email: GET /users with email=[user_email] parameter. This returns the LearnWorlds user object with its own internal id field. Store this LearnWorlds user ID in a private Text field on the Bubble User data type. Use the stored LearnWorlds ID (not Bubble's ID) when calling the enrollment endpoint.

## Frequently asked questions

### Why does LearnWorlds require two headers instead of just the API key?

LearnWorlds routes API requests through your school's own subdomain (mycompany.learnworlds.com), not a centralized API host. The Lw-Client header identifies which school the request belongs to, while Authorization: Bearer authenticates it. Both are required on every request — missing the Lw-Client header causes a 401 with no clear error message indicating which header is the problem.

### Do I need the Pro plan just to test the integration?

Yes — LearnWorlds API access is exclusively a Pro plan feature. There is no free trial of API access, and the API section does not appear in School Settings on Starter or lower plans. If you want to evaluate the integration before committing to the Pro plan, request a demo from LearnWorlds and ask their team to provide test API credentials.

### Can I build a public certificate verification page without requiring users to log in?

Yes, and this is one of the best use cases for the LearnWorlds + Bubble combination. Create a Bubble page at /verify/[certificateId], use a Backend Workflow to call GET /api/v2/certificates/{id} server-side, and display the results publicly. Enable 'Ignore privacy rules when running via Workflow API' on the Backend Workflow so it runs without requiring an active Bubble user session. Students share the /verify URL on LinkedIn, resumes, or badges.

### How do I link Bubble users to LearnWorlds users?

When a user logs into your Bubble app for the first time, call GET /users with an email URL parameter matching their Bubble account email. LearnWorlds returns the user object including their internal id field. Store this LearnWorlds user ID in a private field on the Bubble User data type (mark the field Private in Data → Privacy). Use this stored ID — not Bubble's own user ID — for all subsequent enrollment and certificate calls.

### Why do dates from LearnWorlds show as numbers in my Bubble app?

LearnWorlds API returns all timestamps as Unix epoch integers in seconds (e.g., 1700000000 for November 14, 2023). Bubble's :formatted as date operator expects milliseconds. The fix: multiply the field value by 1000 in Bubble's expression builder (using a Calculate expression), then apply :formatted as date to the result. This converts seconds to milliseconds, which Bubble correctly interprets as a date.

### Is it safe to store the LearnWorlds API key in Bubble?

Yes — when the Authorization header is marked Private in Bubble's API Connector, the key is stored server-side and never transmitted to the browser. Bubble's API Connector runs from Bubble's own servers, so user browser network inspectors cannot see the key in transit. The risk is on the Bubble configuration side: never put the API key in a plain-text App Setting that's publicly readable, a client-side JavaScript action, or a Bubble Option Set. Keep it exclusively in the Private header of the API Connector.

---

Source: https://www.rapidevelopers.com/bubble-integrations/learnworlds
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/learnworlds
