Skip to content
Learnomy

Incoming Enrollment Webhook

The incoming enrollment webhook lets an outside system grant or revoke access to a Learnomy course after a sale happens somewhere else. You sell a course on your own checkout, Gumroad, a CRM, or any other platform, and that platform tells Learnomy to enroll the buyer by POSTing a signed request to one endpoint.

This is built for courses set to the Access granted externally (closed) pricing model. Closed courses are not sold on your Learnomy site, so this webhook is how buyers actually get in.

What you can do

  • Enroll a buyer in a closed course as soon as they pay on an external platform, with no manual work.
  • Revoke access automatically when the external platform reports a refund or cancellation.
  • Create the buyer's account on the fly if they do not already have one, so a first-time customer is never turned away.
  • Record an external order against the enrollment (amount, currency, provider) so you have a receipt for support and refund reconciliation.
  • Send a signed test enrollment from the admin screen to confirm the whole chain works before you connect your store.
  • Connect through Zapier or Make with a no-code "New sale -> POST" recipe, or POST directly from your own backend.

Incoming enrollment recipe panel

How to use it

Step 1 -- set the course to closed pricing

The webhook only manages courses whose access is granted externally. Open the course editor, go to the pricing settings, and choose Access granted externally (closed). Any course that is free, one-time, or membership-priced is rejected by the endpoint, so this step comes first.

Step 2 -- open the Webhooks screen

Go to Learnomy Settings -> Webhooks (?page=learnomy-webhooks). The Incoming enrollment (external / closed courses) panel is at the top of the page.

Step 3 -- copy the endpoint URL

The Endpoint URL field holds the address your store will POST to:

POST /wp-json/learnomy/v1/webhooks/enrollment

Use the copy button next to the field. Your external platform sends a JSON body to this URL to grant or revoke access.

Step 4 -- set and copy the signing secret

The signature is mandatory. If no signing secret is set, the endpoint refuses every request, so this is not optional.

  1. In the Signing secret field, click Regenerate to create a secret (Learnomy stores it as the learnomy_incoming_webhook_secret option).
  2. Use the show/hide and copy buttons to reveal and copy it.
  3. Paste the secret into the external system that will sign the requests.

Regenerating the secret breaks any connection still using the old value until you update the secret there too.

Step 5 -- sign each request

Your external system builds a JSON body and signs it:

  • Body (minimum): { "user_email": "buyer@example.com", "course_id": 123 }
  • Signature: HMAC-SHA256 of the exact JSON body, keyed with your signing secret.
  • Header: send the signature as X-Learnomy-Signature.

To grant access, send "action": "enroll" (this is the default if action is omitted). To pull access on a refund, send "action": "revoke".

Step 6 -- send a test enrollment

Before wiring your store, confirm the chain works from inside wp-admin:

  1. In the Send a test enrollment row, pick one of your closed courses from the dropdown.
  2. Enter a buyer email (your own email is pre-filled).
  3. Click Send test. Learnomy signs a real request with your secret and runs it through the live endpoint, then shows a pass or fail result.

If no course is set to closed pricing yet, this row tells you to set one first.

Step 7 -- connect your store

Wire the trigger in Zapier, Make, or your own backend, following the recipe shown in the Connect your store (Zapier / Make) row:

  1. Trigger: "New sale" in your store (Gumroad, Stripe, WooCommerce, and so on).
  2. Action: "Webhooks - POST" to the endpoint URL, JSON body { user_email, course_id }, with the X-Learnomy-Signature header set to the HMAC-SHA256 of the body.
  3. Refund recipe: trigger on "Refund" and send the same POST with "action": "revoke".

Settings & options

The panel lives on the Webhooks admin screen. The fields it manages:

Field What it is
Endpoint URL Read-only. POST /wp-json/learnomy/v1/webhooks/enrollment.
Signing secret Stored in the learnomy_incoming_webhook_secret option. Shown, copied, and regenerated from the panel. Never returned by any read endpoint.
Send a test enrollment Course selector (your closed courses) plus a buyer email, fires a real signed test.

The JSON body accepted by the endpoint:

Field Required Notes
user_email Yes The buyer. Provisioned as a new account if it does not exist.
course_id Yes Must be a course set to closed pricing, or the request is rejected with 409.
action No enroll (default) or revoke.
external_ref No Your external order or reference id. Used for idempotency and reconciliation. A repeated external_ref will not double-enroll or double-charge.
amount No Order amount recorded on the transaction. Defaults to 0.
currency No Three-letter currency code. Defaults to USD.
full_name No Used when provisioning a new buyer account.
provider No Name of the external system that sold the course, recorded on the transaction. Defaults to external.

Behavior worth knowing:

  • The signature is always required. With no secret configured the endpoint returns 401; a missing signature returns 401; a signature that does not match returns 403.
  • Only closed courses are managed here. A free, priced, or membership course returns 409.
  • Enroll records an external transaction (marked completed) and enrolls the buyer with source = webhook.
  • Revoke pulls the seat. Revoking a buyer who does not exist, or a seat that is already gone, is treated as a success so retries are safe.

For developers

REST routes (all under the learnomy/v1 namespace):

  • POST /webhooks/enrollment -- the public incoming trigger. Permission is __return_true because a mandatory HMAC signature (X-Learnomy-Signature) is verified inside the handler.
  • POST /admin/webhooks/incoming/regenerate-secret -- rotates the signing secret (admin only). Returns the new secret once.
  • POST /admin/webhooks/incoming/test -- fires a real signed test enrollment against a chosen course_id and user_email (admin only).

Hooks:

  • learnomy_incoming_webhook_payload (filter) -- normalize a third-party payload shape into Learnomy's { user_email, course_id, action, external_ref, amount, currency, full_name, provider } params. Use this to adapt a provider that posts a different body (for example Gumroad, Teachable, or Podia). Passed the payload array and the WP_REST_Request.
  • learnomy_external_enrollment_requested (action) -- fires after a successful external enroll, with $user_id, $course_id, and the WP_REST_Request. Intended for add-ons and xAPI notifications; the enrollment itself is already done.

Options:

  • learnomy_incoming_webhook_secret -- the shared HMAC-SHA256 signing secret.

New buyers are resolved or created through Provisioning_Service::ensure_user_by_email(), and the external order is written to lrn_transactions (idempotent on external_ref via Transaction::find_by_provider_id()).

Related