ClassMarker webhooks
Feeling a bit lost? Have a quick look over the ClassMarker concepts.
You can use ClassMarker webhooks to receive exam results in real time. You generate, view and manage your ClassMarker webhook in your ClassMarker account.
ClassMarker webhooks are built to allow developers to download and store results in their own databases. See our code examples in GitHub, including PHP code and database table schemas to help get you started or as a guide for coding in other languages.
When receiving exam results using ClassMarker webhooks, results are returned from their respective groups or links, depending on how they were taken.
Get started
Add the webhook code to your app
Add your webhook code to an app at a publicly accessible URL on your server (which you should keep a secret).
Create your webhook
- Log in to your ClassMarker account.
- Navigate to My Account > Webhooks / API Keys.
- In the Webhooks tab, click New Webhook +.
- Enter the values for the webhook:
- Specify a name
- Add your publicly accessible URL to the endpoint field
- Set other options to include as needed:
- Questions, responses and category results
- Unique link ID for links
- URL for viewing results
- Select the Save Webhook Settings button.
- Copy the webhook secret phrase created when you generated the webhook in ClassMarker and replace it in your app (see
YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE
in the code examples).
Test your webhook
- In ClassMarker, Check the Verify on Save checkbox.
- Select the Save Webhook Settings button.
If you receive a 2XX header response from your server, your webhook is active and you can select it when assigning exams to be taken (such as a 200 response, or a 404 response if your endpoint URL is incorrect).
Server response
Your server should respond with a 200 or any 2XX response code. No response body is required.
If a 2XX is not received, the webhook is retried.
To verify the payload from ClassMarker, encrypt your JSON payload with your unique webhook secret.
See How to verify webhook payloads.
Testing code locally?
You may find https://ngrok.com/ useful to setup a secure URL to your localhost server.
Assign the webhook to a group or link
The final step is to assign the webhook to a group or link, either when assigning an exam or by updating an existing exam's settings in your ClassMarker account.
You can use the same webhook for all your groups and links or create multiple webhooks for separate groups and links.
Example code
You can find code examples for different languages in our Webhook code examples page.
You can also browse through more examples in our GitHub repository:
PHP: https://github.com/classmarker/retrieve-quiz-results-webhooks-php
Ruby: https://github.com/classmarker/retrieve-quiz-results-webhooks-ruby
Python: https://github.com/classmarker/retrieve-quiz-results-webhooks-python
Go: https://github.com/classmarker/retrieve-quiz-results-webhooks-go
Node https://github.com/classmarker/retrieve-quiz-results-webhooks-node
Java: https://github.com/classmarker/retrieve-quiz-results-webhooks-java
C#: https://github.com/classmarker/retrieve-quiz-results-webhooks-csharp
Checking for updated results
After exams are completed (and you have received the original results payload), the results may change, so you need to build some logic into your script to check for this.
The results may change due to the following scenarios:
- The exam requires grading (essay question types only).
- The exam is re-graded manually (administrators may want to change points for an answer).
In both cases, exam givers can manually resend results via the webhook (from the results pages after grading) to update your system with the upgraded score.
We recommend that when you build your script to receive test results via webhooks, it first checks if the result already exists and updates it, rather then inserting it again.
You can use the fields listed below to check if a test result already exists in your database.
For groups, these four values together are unique per test, per user:
user_id
test_id
group_id
time_started
This is because the same exam can be assigned to multiple groups, so you need to check the group ID along with the test ID (the same user can take the same exam under multiple groups).
For links, you only need to check a single field for unique link results:
link_result_id
Response format
HTML formatting in exam questions and feedback
Questions (including answer options) and feedback fields can contain HTML formatting as set by administrators in our WYSIWYG editors (e.g. span, br, b, i, u, a, img). These HTML tags will be sent as plain HTML ready for display on the web.
Any non-HTML formatting tags will be turned into HTML entities (so they will display the HTML tags rather than pass them). For example, a question might ask "What HTML tag is this: <br>?". This will display the break tag, not create a line break.
Because users can type text responses in some question types, such as free text or essay questions, you must convert special characters in this text to HTML entities so HTML is displayed on the page as HTML tags and not parsed.
Response object
The following fields appear in all responses:
Key | Description |
---|---|
payload_type |
Group or link |
payload_status |
live when showing actual results, verify when setting up the webhook in your account |
Group results
Example response - single exam result taken by a user registered in a group
{
"payload_type": "single_user_test_results_group",
"payload_status": "live",
"test": {
"test_id": 103,
"test_name": "Sample Test Name"
},
"group": {
"group_id": 104,
"group_name": "Sample Group Name"
},
"result": {
"user_id": "3276524",
"first": "Mary",
"last": "Williams",
"email": "mary@example.com",
"percentage": 75,
"points_scored": 9,
"points_available": 12,
"requires_grading": "Yes",
"time_started": 1436263102,
"time_finished": 1436263702,
"duration": "00:05:40",
"percentage_passmark": 50,
"passed": true,
"feedback": "Thanks for completing our Exam!",
"give_certificate_only_when_passed": false,
"certificate_url": "https://www.classmarker.com/pdf/certificate/SampleCertificate.pdf",
"certificate_serial": "CLPPYQSBSY-ZZVKJGQH-XHWMMRCHYT",
"view_results_url": "https://www.classmarker.com/view/results/?required_parameters_here"
},
"questions": [
{
"question_id": 3542854,
"question_type": "multiplechoice",
"category_id": 1,
"points_available": 2,
"question": "What is the first step for treating a skin burn?",
"options": {
"A": "Apply oil or butter",
"B": "Nothing should be done",
"C": "Soak in water for five minutes",
"D": "Apply antibiotic ointment"
},
"correct_option": "C",
"points_scored": 2,
"user_response": "C",
"result": "correct",
"feedback": "Great, and remember, never use oil on Skin burns!"
},
{
"question_id": 10254859,
"question_type": "multiplechoice",
"category_id": 2,
"points_available": 2,
"question": "Select the options you should take when the fire alarm sounds:",
"options": {
"A": "Call you manager to see if you can leave the building",
"B": "Exit the building immediately",
"C": "Use the Lifts to exit faster",
"D": "Use the stairwell to exit"
},
"correct_option": "B,D",
"points_scored": 1,
"user_response": "B",
"result": "partial_correct",
"feedback": "That is incorrect, the correct answers are A and C"
},
{
"question_id": 5485962,
"question_type": "truefalse",
"category_id": 3,
"points_available": 1,
"question": "Our Support staff work 7 day a week",
"options": {
"A": "True",
"B": "False"
},
"correct_option": "A",
"points_scored": 1,
"user_response": "A",
"result": "correct",
"feedback": "That is correct, we provide 7 day support"
},
{
"question_id": 3896152,
"question_type": "freetext",
"category_id": 5,
"points_available": 1,
"question": "Our company website is: www.______.com",
"options": {
"exact_match": [
{
"content": "example"
},
{
"content": "example.com"
},
{
"content": "www.example.com"
},
{
"content": "http://www.example.com"
},
{
"content": "https://www.example.com"
}
]
},
"points_scored": 1,
"user_response": "example",
"result": "correct",
"feedback": "Correct, always send our customers to our main website: www.example.com"
},
{
"question_id": 6403973,
"question_type": "matching",
"category_id": 2,
"points_available": 4,
"question": "Match the options below:",
"points_scored": 3,
"options": {
"A": {
"clue": "Product faulty",
"match": "Exchange or Refund",
"correct_option": "A",
"user_response": "A"
},
"B": {
"clue": "Customer mis-used and broke product",
"match": "No refund",
"correct_option": "B",
"user_response": "B"
},
"C": {
"clue": "Customer broke factory seal",
"match": "No refund",
"correct_option": "B", // In this case, the exam administrator has set two matches to equal the same clue "B"
"user_response": "B"
},
"D": {
"clue": "Incorrect product size purchased",
"match": "Exchange",
"correct_option": "D",
"user_response": "A"
},
"E": {
"match": "Have customer removed by security" // This is an incorrect match with no corresponding clue, these can be used to help make matching questions more difficult
}
},
"result": "partial_correct",
"feedback": "Please check your incorrect matches"
},
{
"question_id": 444564,
"question_type": "essay",
"category_id": 5,
"points_available": 1,
"question": "Describe some advantages of having test papers graded instantly:",
"points_scored": 0,
"user_response": "Users can see their results instantly, grading is accurate, save time from manual grading",
"result": "requires_grading",
"custom_feedback": "",
"feedback": "Generic feedback here"
},
{
"question_id": 442810,
"question_type": "grammar",
"category_id": 3,
"points_available": 1,
"question": "The car was parkked over their!",
"answer": "The car was parked over there!",
"points_scored": 1,
"user_response": "The car was parked over there!",
"result": "correct",
"feedback": "Well done!"
}
],
"category_results": [
{
"category_id": 1,
"name": "Health and Safety",
"percentage": 66.7,
"points_available": 6,
"points_scored": 4
},
{
"category_id": 2,
"name": "Exit Procedure",
"percentage": 100,
"points_available": 2,
"points_scored": 2
},
{
"category_id": 3,
"name": "General Knowledge",
"percentage": 100,
"points_available": 2,
"points_scored": 2
},
{
"category_id": 5,
"name": "Sales",
"percentage": 50,
"points_available": 2,
"points_scored": 1
}
]
}
The following fields appear in the result
section of a group payload:
Key | Description |
---|---|
user_id |
Each registered user has a unique ID in ClassMarker. |
first |
First name. |
last |
Last name. |
email |
Users email. |
test_id |
Each exam has a unique ID in ClassMarker. |
group_id |
Each group has a unique ID in ClassMarker. |
percentage |
0 - 100 can include 1 decimal place. |
points_scored |
Total points scored. Can include 1 decimal place. |
points_available |
Total points available. Can include 1 decimal place. |
test_id |
Each exam has a unique ID in ClassMarker. |
time_started |
Because registered users can retake exams (if allowed), you can use this timestamp to understand if this is a new or updated (regraded) test result. This timestamp is never updated, so if you receive the same timestamp twice for the same user and exam, you know the results have been resent manually. |
time_finished |
Can update on re-sends (for example, an exam is re-opened by an administrator to give a user more time). |
duration |
Exam duration. |
passed |
true or false ; true is returned if no Passmark is set for an exam. |
feedback |
Optional exam feedback, as set by administrators. |
percentage_passmark |
Percent required to pass exam. |
requires_grading |
Yes or No . Only exams that include essay style questions require grading. Score may not be considered final. |
certificate_url |
Unique Link to download users certificate. |
certificate_serial |
Unique serial number for certificate. |
give_certificate_only_when_passed |
true or false ; exams can be set up to disallow certificate downloading if a user fails the exam. |
view_results_url |
Unique URL per exam result for viewing formatted exam results in ClassMarker (without being logged into a ClassMarker account). You need to set up an access control password for viewing access (see URL for viewing results for details). |
Link results
Example response - single exam result taken via a link
{
"payload_type": "single_user_test_results_link",
"payload_status": "live",
"test": {
"test_id": 100,
"test_name": "Sample Test Name"
},
"link": {
"link_id": 101,
"link_name": "Sample Link Name",
"link_url_id": "sample_quiz_id_123"
},
"result": {
"link_result_id": 8127364,
"first": "John",
"last": "Smith",
"email": "john@example.com",
"percentage": 75.0,
"points_scored": 9.0,
"points_available": 12.0,
"requires_grading": "Yes",
"time_started": 1436263522,
"time_finished": 1436264122,
"duration": "00:05:20",
"percentage_passmark": 50,
"passed": true,
"feedback": "Thanks for completing our Exam!",
"give_certificate_only_when_passed": false,
"certificate_url": "https://www.classmarker.com/pdf/certificate/SampleCertificate.pdf",
"certificate_serial":"CLPPYQSBSY-ZZVKJGQH-XHWMMRCHYT",
"view_results_url": "https://www.classmarker.com/view/results/?required_parameters_here",
"access_code_question": "What is your Employee ID?",
"access_code_used": "12345",
"extra_info_question": "Which sales department are you assigned to?",
"extra_info_answer": "New York Product 7 Divisiaon",
"extra_info2_question": "Extra Information Question 2 here",
"extra_info2_answer": "Extra Information Answer 2 here",
"extra_info3_question": "Extra Information Question 3 here",
"extra_info3_answer": "Extra Information Answer 3 here",
"extra_info4_question": "Extra Information Question 4 here",
"extra_info4_answer": "Extra Information Answer 4 here",
"extra_info5_question": "Extra Information Question 5 here",
"extra_info5_answer": "Extra Information Answer 5 here",
"cm_user_id": "123456",
"ip_address": "192.168.0.1"
},
"questions": [
{
"question_id": 3542854,
"question_type": "multiplechoice",
"category_id": 1,
"points_available": 2,
"question": "What is the first step for treating a skin burn?",
"options": {
"A": "Apply oil or butter",
"B": "Nothing should be done",
"C": "Soak in water for five minutes",
"D": "Apply antibiotic ointment"
},
"correct_option": "C",
"points_scored": 2,
"user_response": "C",
"result": "correct",
"feedback": "Great, and remember, never use oil on Skin burns!"
},
{
"question_id": 10254859,
"question_type": "multiplechoice",
"category_id": 2,
"points_available": 2,
"question": "Select the options you should take when the fire alarm sounds:",
"options": {
"A": "Call you manager to see if you can leave the building",
"B": "Exit the building immediately",
"C": "Use the Lifts to exit faster",
"D": "Use the stairwell to exit"
},
"correct_option": "B,D", // Note this multiple choice question contains 2 correct options.
"points_scored": 1,
"user_response": "B", // Only one option selected, hence 1/2 points awarded. Format for multiple selected options: A,B,C etc.
"result": "partial_correct",
"feedback": "That is incorrect, the correct answers are A and C"
},
{
"question_id": 5485962,
"question_type": "truefalse",
"category_id": 3,
"points_available": 1,
"question": "Our Support staff work 7 day a week",
"options": {
"A": "True",
"B": "False"
},
"correct_option": "A",
"points_scored": 1,
"user_response": "A",
"result": "correct",
"feedback": "That is correct, we provide 7 day support"
},
{
"question_id": 3896152,
"question_type": "freetext",
"category_id": 5,
"points_available": 1,
"question": "Our company website is: www.______.com",
"options": {
"exact_match": [
{
"content": "example"
},
{
"content": "example.com"
},
{
"content": "www.example.com"
},
{
"content": "http://www.example.com"
},
{
"content": "https://www.example.com"
}
]
},
"points_scored": 1,
"user_response": "example",
"result": "correct",
"feedback": "Correct, always send our customers to our main website: www.example.com"
},
{
"question_id": 6403973,
"question_type": "matching",
"category_id": 2,
"points_available": 4,
"question": "Match the options below:",
"points_scored": 3,
"options": {
"A": {
"clue": "Product faulty",
"match": "Exchange or Refund",
"correct_option": "A",
"user_response": "A"
},
"B": {
"clue": "Customer mis-used and broke product",
"match": "No refund",
"correct_option": "B",
"user_response": "B"
},
"C": {
"clue": "Customer broke factory seal",
"match": "No refund",
"correct_option": "B", // Note: The Exam administrator has set two Matches to equal the same Clue "B".
"user_response": "B"
},
"D": {
"clue": "Incorrect product size purchased",
"match": "Exchange",
"correct_option": "D",
"user_response": "A"
},
"E": {
"match": "Have customer removed by security" // This is an incorrect match with no corresponding clue. Can be used to help make matching questions more difficult.
}
},
"result": "partial_correct",
"feedback": "Please check your incorrect matches"
},
{
"question_id": 444564,
"question_type": "essay",
"category_id": 5,
"points_available": 1,
"question": "Describe some advantages of having test papers graded instantly:",
"points_scored": 0,
"user_response": "Users can see their results instantly, grading is accurate, save time from manual grading",
"result": "requires_grading",
"custom_feedback": "",
"feedback": "Generic feedback here"
},
{
"question_id": 442810,
"question_type": "grammar",
"category_id": 3,
"points_available": 1,
"question": "The car was parkked over their!",
"answer": "The car was parked over there!",
"points_scored": 1,
"user_response": "The car was parked over there!",
"result": "correct",
"feedback": "Well done!"
}
],
"category_results": [
{
"category_id": 1,
"name": "Health and Safety",
"percentage": 66.7,
"points_available": 6,
"points_scored": 4
},
{
"category_id": 2,
"name": "Exit Procedure",
"percentage": 100,
"points_available": 2,
"points_scored": 2
},
{
"category_id": 3,
"name": "General Knowledge",
"percentage": 100,
"points_available": 2,
"points_scored": 2
},
{
"category_id": 5,
"name": "Sales",
"percentage": 50,
"points_available": 2,
"points_scored": 1
}
]
}
The following fields appear in the result
section of a link payload:
Key | Description |
---|---|
link_result_id |
Each link result has a unique link result ID. |
first |
First name. |
last |
Last name. |
email |
Users email. |
test_id |
Each exam has a unique ID in ClassMarker. |
link_id |
Each link has a unique ID in ClassMarker. |
percentage |
0 - 100 can include 1 decimal place. |
points_scored |
Total points scored. Can include 1 decimal place. |
points_available |
Total points available. Can include 1 decimal place. |
time_started |
See Group results |
time_finished |
See Group results |
duration |
See Group results |
passed |
See Group results |
feedback |
See Group results |
percentage_passmark |
See Group results |
requires_grading |
See Group results |
certificate_url |
See Group results |
certificate_serial |
See Group results |
give_certificate_only_when_passed |
See Group results |
view_results_url |
See Group results |
access_code_question |
Question user needs to answer if using access lists. |
access_code |
Access code used to access test. |
extra_info_question |
Optional non-graded question asked before exam is started. |
extra_info |
Users answer 1. |
extra_info2_question |
Optional non-graded question asked before exam is started. |
extra_info2 |
Users answer 2. |
extra_info3_question |
Optional non-graded question asked before exam is started. |
extra_info3 |
Users answer 3. |
extra_info4_question |
Optional non-graded question asked before exam is started. |
extra_info4 |
Users answer 4. |
extra_info5_question |
Optional non-graded question asked before exam is started. |
extra_info5 |
Users answer 5. |
cm_user_id |
Optional tracking IDs sent to your system (see Use your own user IDs with ClassMarker). |
ip_address |
Users IP Address. |
Questions
The following fields appear in the questions
section of group and link payloads, if enabled in your webhook settings.
Key | Description |
---|---|
question_id |
Each question has a unique ID in ClassMarker |
question_type |
multiplechoice , truefalse , freetext , matching , essay , grammar |
category_id |
Each question can be categorized. Category has a unique ID |
points_available |
Total points question is worth. Can be to one decimal place |
question |
Question text. Can contain HTML formatting |
options |
Contains answer options for question types with multiple options. Can contain HTML formatting |
points_scored |
Points given for the answer. Can be to one decimal place |
user_response |
Test taker's selected option or typed answer. If no response given, this key is not returned |
result |
Answer result, any of the following: correct , partial_correct , incorrect , requires_grading , unanswered |
feedback |
Feedback for the answer. If no feedback, this key is not returned. Can contain HTML formatting |
custom_feedback |
Only used for essay questions. Exam givers/admins can add feedback when grading questions |
correct_option |
One or more correct answers (can be more than one for multiple choice questions) |
Category results
The following fields appear in the category_results
section of group and link payloads, if enabled in your webhook settings.
Key | Description |
---|---|
category_id |
Each question can be categorized. Category has a unique ID |
name |
Name given to a category by administrator |
percentage |
Can be to one decimal place |
points_available |
Can be to one decimal place |
points_scored |
Can be to one decimal place |
Send exam results manually
You can manually send results via your webhook from individual exam results pages, by clicking Send result to webhook.
URL for viewing results
You can give access to colleagues who need to review formatted exam results (including questions and answers) without a ClassMarker account by sharing the value retrieved in view_results_url
(you can see this value in the example responses in the Response object section).
Including this URL is optional and it needs to be enabled in the webhook settings page. Each URL is unique and unguessable.
To enable view_results_url
:
- In your ClassMarker My account page, scroll down to the API section and click to expand it.
- In View Results URL - Access Control Settings, update your control settings as required.
- Select Update Access Settings.
- Navigate to My Account > Webhooks / API Keys.
- Create or edit a webhook, and check the View Results URL option.
- Select Save Webhook Settings.
Failed payload retry rules
We understand getting results back to your system in real time can be critical, which is why ClassMarker retries a failed webhook within minutes of it first failing. This is in case your system has a brief issue at the exact time of the payload delivery. You will receive your results soon after.
Failed webhook scenario
- An exam has finished and the first webhook attempt is triggered.
- The webhook fails (a 2XX response is not received) and it is tried for a second time within minutes.
- If the webhook fails a second time, ClassMarker will then reattempt the single exam result webhook once per hour for 72 hours.
- If your server fails to successfully respond to the webhook 1000 times in a row (this includes retry attempts for all results it is trying to send), the webhook will be set to inactive and no longer attempt to deliver new results or retry undelivered results until you activate the webhook again.
- If you have multiple webhooks set up, only the webhook that has failed 1000 times in a row will be set to inactive. All your other webhooks will continue to run as normal.
Failed attempt reset
The counter resets to 0 after a successful response. So, for example, if your webhook has failed seven times in a row but then returns a successful 2XX response, the failed requests counter resets to 0.
Inactive webhooks
When a webhook is inactive, exam results are not sent and not enabled for retry. You need to send those exam results manually from the test results page instead.
Email alerts
ClassMarker sends an email alert the first time a webhook fails, then periodically on failed attempts until either your webhook endpoint starts returning 2XX responses again, or your webhook becomes inactive, in which case you will be notified it has become inactive.
How to verify webhook payloads
When a webhook payload is delivered to your system, you can verify it came from ClassMarker by using the secret phrase generated when creating your webhook. Only you and ClassMarker know your secret phrase.
ClassMarker creates a signature (base64 hash using HMAC SHA256) using the JSON payload and your secret phrase, and sends the signature in the HTTP header: X-Classmarker-Hmac-Sha256
for you to verify your end.
To verify the payload came from ClassMarker, encrypt your JSON payload with ClassMarker's webhook secret, as shown in the PHP example you can see by selecting the PHP language tab.
You can find more examples in our GitHub code examples.
<?php
define('CLASSMARKER_WEBHOOK_SECRET', 'YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE');
$calculated_signature = base64_encode(hash_hmac('sha256', $json_data, CLASSMARKER_WEBHOOK_SECRET, true));
$header_hmac_signature = $_SERVER['HTTP_X_CLASSMARKER_HMAC_SHA256'];
return ($header_hmac_signature == $calculated_signature);
Troubleshooting
Here are some tips to debug payload problems:
Debug with a third party website such as https://webhook.site. You can use it to view exam results sent by your webhook.
- Go to https://webhook.site to get a unique URL to use as a temporary endpoint URL.
- Follow the instructions in Create your webhook and Test your webhook to add your temporary URL to your webhook.
- After saving, you can view the sample example results on https://webhook.site.
Check the
payload_status
field is set tolive
.Check your system responses, such as with third party website http://onlinecurl.com/. This may highlight for example, a SSL certificate issue.