Reach 360: Webhook Events
Article Last Updated
This article applies to:
Event Types
course.completed
- Triggered when a learner completes a course.course.submitted
- Triggered when an author submits a course to be reviewed by an admin for publication.enrollments.created
- Triggered when users or groups are enrolled in either a course or learning path.user.created
- Triggered when a user is added to your Reach 360 account.
Webhook Event Payload
Each webhook request's payload has a common structure shared by all webhook events. The body of the request contains the following properties:
id
(string) - the unique identifier of the eventcreatedAt
(string) - the time at which the event happenedtype
(string) - the webhook event typewebhookId
(string) - the id of the webhook which resulted in the event being sentapiVersion
(string) - API Version used when sending the webhook event. Will also be sent as aAPI-Version
header in the requestdata
(object) - specific to the type of webhook event type (see examples)
Request Payload Examples
Each webhook event example below shows an example of what a webhook request to the target URL on your server would look like.
course.completed
Triggered when a learner completes a course.
Event payload data properties:
-
course (object) - the Course Object for the related course or null if the enrollments are for a learning path. This course object also has a quiz object with two properties: passed and score. If there is no quiz associated with the course, then the object will be empty.
-
user (object) - the User Object for the related user.
{ "id": "example-course-completed-event-id", "createdAt": "2020-07-02T03:39:18.991Z", "type": "course.completed", "webhookId": "example-webhook-id", "apiVersion": "2023-04-04", "data": { "course": { "authorUrl": "https://api.reach360.com/users/example-author-id", "coverImageUrl": null, "id": "example-course-id", "courseReportUrl": "https://api.reach360.com/reports/courses/example-course-id", "title": "Example Course", "url": "https://api.reach360.com/courses/example-course-id", "contentType": "RISE" "quiz": { "passed": true, "score": 80 } }, "user": { "email": "foo@example.com", "firstName": "Example First Name", "groupsUrl": "https://api.reach360.com/users/example-user-id/groups", "id": "example-user-id", "lastName": "Example Last Name", "lastActiveAt": "2021-10-28T20:39:52.659Z", "learnerReportUrl": "https://api.reach360.com/reports/learners/example-user-id", "role": "learner", "url": "https://api.reach360.com/users/example-user-id", "articulate360User": false } } }
course.submitted
Triggered when an author submits a course to be reviewed by an admin for publication.
{ "id": "example-course-submitted-event-id", "createdAt": "2020-09-14T05:47:28.951Z", "type": "course.submitted", "webhookId": "example-webhook-id", "apiVersion": "
2023-04-04", "data": { "isInitialSubmission": true, "course": { "authorUrl": "<https://api.reach360.com/users/example-author-id>", "coverImageUrl": null, "id": "example-course-id", "courseReportUrl": "<https://api.reach360.com/reports/courses/example-course-id>", "title": "Example Course", "url": "<https://api.reach360.com/courses/example-course-id>", "contentType": "RISE" }, "submitter": { "email": "author@example.com", "firstName": "Example", "groupsUrl": "<https://api.reach360.com/users/example-author-id/groups>", "id": "example-author-id", "lastName": "Author", "lastActiveAt": "2021-10-28T20:39:52.659Z", "learnerReportUrl": "<https://api.reach360.com/reports/learners/example-author-id>", "role": "author", "url": "<https://api.reach360.com/users/example-author-id>"
"articulate360User": false}, "reviewer": { "email": "admin@example.com", "firstName": "Example", "groupsUrl": "<https://api.reach360.com/users/example-admin-id/groups>", "id": "example-admin-id", "lastName": "Admin", "lastActiveAt": "2021-10-28T20:39:52.659Z", "learnerReportUrl": "<https://api.reach360.com/reports/learners/example-admin-id>", "role": "admin", "url": "<https://api.reach360.com/users/example-admin-id>"
"articulate360User": false} } }
enrollments.created
Triggered when users or groups are enrolled in either a course or learning path.
This event's payload always contains the same property names, but which of those properties have values varies depending on the type of enrollments that were created.
For example, if one or more users are enrolled in a course, the data
portion of the payload sent to your handler will include a users
array property (an array of User Objects) and a course
property. In this scenario the groups
property will be an empty array and learningPath
will be set to null
.
Similarly, if one or more groups are enrolled in a learning path, the data
object will have a groups
array property (an array of Group Objects) and a learningPath
property, while users
will be an empty array and course
will be null
.
Event payload data
properties:
- course (object) - the Course Object for the related course or
null
if the enrollments are for a learning path. - learningPath (object) - the Learning Path Object for the related learning path or
null
if the enrollments are for a course. - enrolledBy (object) - The User Object of the user that created the enrollments or
null
if the enrollment was created through the Course Enrollments API or Learning Path Enrollments API. - groups (list of objects) - A list of Group Objects that were enrolled (may be empty).
- users (list of objects) - A list of User Objects that were enrolled (may be empty).
{ "id": "example-enrollments-created-event-id", "type": "enrollments.created", "createdAt": "2020-09-16T19:59:55.912Z", "data": { "course": { "authorUrl": "<https://api.reach360.com/users/example-author-id>", "coverImageUrl": null, "id": "example-course-id", "courseReportUrl": "<https://api.reach360.com/reports/courses/example-course-id>", "title": "Example Course", "url": "<https://api.reach360.com/courses/example-course-id>" "source": "rise" }, "enrolledBy": { "email": "admin@example.com", "firstName": "Example", "groupsUrl": "<https://api.reach360.com/users/example-admin-id/groups>", "id": "example-admin-id", "lastName": "Admin", "lastActiveAt": "2021-10-28T20:39:52.659Z", "learnerReportUrl": "<https://api.reach360.com/reports/learners/example-admin-id>", "role": "admin", "url": "<https://api.rise.com/users/example-admin-id>"
"articulate360User": false}, "groups": [], "learningPath": null, "users": [ { "email": "learner1@example.com", "firstName": "Foo", "groupsUrl": "<https://api.reach360.com/users/example-learner-1/groups>", "id": "example-learner-1", "lastName": "Learner", "lastActiveAt": "2021-10-28T20:39:52.659Z", "learnerReportUrl": "<https://api.reach360.com/reports/learners/example-learner-1>", "role": "learner", "url": "<https://api.reach360.com/users/example-learner-1>"
"articulate360User": false}, { "email": "learner2@example.com", "firstName": "Bar", "groupsUrl": "<https://api.reach360.com/users/example-learner-2/groups>", "id": "example-learner-2", "lastName": "Learner", "lastActiveAt": "2021-10-28T20:39:52.659Z", "learnerReportUrl": "<https://api.reach360.com/reports/learners/example-learner-2>", "role": "learner", "url": "<https://api.reach360.com/users/example-learner-2>"
"articulate360User": false
}, { "email": "learner3@example.com", "firstName": "Baz", "groupsUrl": "<https://api.reach360.com/users/example-learner-3/groups>", "id": "example-learner-3", "lastName": "Learner", "lastActiveAt": "2021-10-28T20:39:52.659Z", "learnerReportUrl": "<https://api.reach360.com/reports/learners/example-learner-3>", "role": "learner", "url": "<https://api.reach360.com/users/example-learner-3>"
"articulate360User": false
}, ] }, "apiVersion": "
2023-04-04", "webhookId": "example-webhook-id" }
user.created
Note: user.created
events will be delayed for SSO users by 5-10 minutes.
Note: user.created
events are NOT triggered for SAML users who are removed from your IDP and added again unless they are also removed from your Reach account.
Triggered when a user is added to your Rise account.
{ "id": "example-user-created-event-id", "createdAt": "2020-08-24T01:36:18.982Z", "type": "user.created", "webhookId": "example-webhook-id", "apiVersion": "
2023-04-04", "data": { "user": { "email": "foo@example.com", "firstName": "Example First Name", "groupsUrl": "<https://api.reach360.com/users/example-user-id/groups>", "id": "example-user-id", "lastName": "Example Last Name", "lastActiveAt": "2021-10-28T20:39:52.659Z", "learnerReportUrl": "<https://api.reach360.com/reports/learners/example-user-id>", "role": "learner", "url": "<https://api.reach360.com/users/example-user-id>" "articulate360User": false
} } }
Verifying Requests
When a webhook is created with a sharedSecret
Rise will include an X-Hook-Signature
header in each request, allowing you to verify it came from Reach. To calculate the signature, Reach uses an HMAC-SHA1 hex digest with the sharedSecret
and the body of the request.
Here's an example of what signature verification code might look like if you're using Node.js (Note: the specifics may differ depending on the framework you're using, the version of Node you're on, and other factors)
const crypto = require('crypto')
const signatureDigest = crypto.createHmac('sha1', process.env.WEBHOOK_SHARED_SECRET)
.update(Buffer.from(JSON.stringify(request.body)))
.digest('hex')
if (request.headers['x-hook-signature'] === signatureDigest) {
// OK: request came from Reach
} else {
// Error: request didn't come from Reach
}
Error Handling and Retries
Rise will send a POST request with JSON data to the configured targetUrl
each time one of the configured events
occurs. Your server acknowledges that it has received the payload by sending back a 200 response. Any response outside of the 200 range indicates an error. In that case, Rise will retry to send the request 14 more times over the next 48 hours.