NAV

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

  1. Log in to your ClassMarker account.
  2. Navigate to My Account > Webhooks / API Keys.
  3. In the Webhooks tab, click New Webhook +.
  4. 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
  5. Select the Save Webhook Settings button.
  6. 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

  1. In ClassMarker, Check the Verify on Save checkbox.
  2. 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.

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:

  1. The exam requires grading (essay question types only).
  2. 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:

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:

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).

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:

  1. In your ClassMarker account, scroll down to the API section and click to expand it.
  2. In View Results URL - Access Control Settings, update your control settings as required.
  3. Select Update Access Settings.
  4. Navigate to My Account > Webhooks / API Keys.
  5. Create or edit a webhook, and check the View Results URL option.
  6. 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

  1. An exam has finished and the first webhook attempt is triggered.
  2. The webhook fails (a 2XX response is not received) and it is tried for a second time within minutes.
  3. If the webhook fails a second time, ClassMarker will then reattempt the single exam result webhook once per hour for 72 hours.
  4. 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.
  5. 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: