Real Time Quiz Results with WebHooks

New documentation available


This page is now replaced with our new and updated developer documentation which has more options available.

Go to our updated Developer documentation

ClassMarker has two integration options for automatically receiving Test results back to your systems/website, Webhooks or API.

You can select which option best suits your requirements, or even use both if you need:

Webhooks Documentation

IN A NUTSHELL: Webhooks can send your Test results to your website in JSON format in Real Time.

Webhooks can be created (and verified by sending Sample Test results to your Endpoint URL) via your My account / Webhooks section of your ClassMarker account when logged in.

Webhook Payloads Examples

Groups: Single Test Result Payload Example


Example JSON format for a Single Test result taken by a user registered under a Group.

{
   "payload_type":"single_user_test_results_group",
   "payload_status":"live",       // "live": Actual Results. "verify": Sample Results when setting up the Webhook from within your account.
   "test":{
      "test_id":103,
      "test_name":"Sample Test Name"
   },
   "group":{
      "group_id":104,
      "group_name":"Sample Group Name"
   },
   "result":{
      "user_id":"3276524",        // Each registered User has a unique ID user_id in ClassMarker.
      "first":"Mary",
      "last":"Williams",
      "email":"mary@example.com",
      "percentage":75.0,
      "points_scored":9.0,
      "points_available":12.0,
      "requires_grading":"Yes",   // Only Exams that include "Essay" style questions will require grading Learn more.
      "time_started":1436263102,  // Never changes per result.
      "time_finished":1436263702, // Can update on re-sends (EG: An Exam is re-opened by an Administrator to give a User more time).
      "duration":"00:05:40",
      "percentage_passmark":50,
      "passed":true,              // true/false  Note: If no "Passmark" is set for an exam, "true" will be used.
      "feedback":"Thanks for completing our Exam!", // Overall exam feedback as set by Administrators. Feedback is optional.
      "give_certificate_only_when_passed":false,    // true/false  Exams can be setup to disallow certificate downloading if a User fails the Exam.
      "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" // Unique URL per Test result for viewing formatted Test results back on ClassMarker (without being logged into a ClassMarker account). An Access control password is required for viewing access. Set this password on your My account page.
   },
  "questions": [ // Questions, User Responses and Category Results are only included if selected in your Webhook settings.
    {
      "question_id": 3542854,
      "question_type": "multiplechoice",
      "category_id": 1,
      "points_available": 2,     // Points can be to one decimal place.
      "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,           // Points can be to one decimal place.
      "user_response": "C",         // If Question was not answered, "user_response" key will not exist.
      "result": "correct",          // Possible Values: correct / partial_correct / incorrect / requires_grading / unanswered.
      "feedback": "Great, and remember, never use oil on Skin burns!"  // Optional: Question Feedback. If no Question Feedback is set, "feedback" key will not exist
    },
    {
      "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",
      "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 a 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": "",   // "custom_feedback" is only used for Essay Questions. Administrators can add when grading Essay Questions.
      "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 results from Questions are only included if selected in your Webhook settings.
    {
      "category_id": 1,
      "name": "Health and Safety",
      "percentage": 66.7,      // Percentage can be to one decimal place.
      "points_available": 6,   // Points can be to one decimal place.
      "points_scored": 4       // Points can be to one decimal place.
    },
    {
      "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
    }
  ]
}

Links: Single Test Result Payload Example


Example JSON format for a Single Test result taken via a Link.

{
  "payload_type": "single_user_test_results_link",
  "payload_status": "live",        // "live": Actual Results. "verify": Sample Results when setting up the Webhook from within your account.
  "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" // This is the Unique Identifier ID that is required to 'Start' a new Test (when taken via Links) EG: https://...?quiz=sample_quiz_id_123
  },
  "result": {
    "link_result_id": 8127364,     // Each result will have a Unique ID.
    "first": "John",
    "last": "Smith",
    "email": "john@example.com",
    "percentage": 75.0,
    "points_scored": 9.0,
    "points_available": 12.0,
    "requires_grading": "Yes",     // Only Exams that include "Essay" style questions will require grading Learn more.
    "time_started": 1436263522,    // Never changes per result.
    "time_finished": 1436264122,   // Can update on re-sends (EG: An Exam is re-opened by an Administrator to give a User more time).
    "duration": "00:05:20",
    "percentage_passmark": 50,
    "passed": true,               // true/false  Note: If no "Passmark" is set for an exam, "true" will be used.
    "feedback": "Thanks for completing our Exam!", // Overall exam feedback as set by Administrators. Feedback is optional.
    "give_certificate_only_when_passed": false,    // true/false  Exams can be setup to disallow certificate downloading if a User fails the Exam.
    "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",  // Unique URL per Test result for viewing formatted Test results back on ClassMarker (without being logged into a ClassMarker account). An Access control password is required for viewing access. Set this password on your My account page.
    "access_code_question": "What is your Employee ID?",                  // Access list usage is optional. Access lists.
    "access_code_used": "12345",
    "extra_info_question": "Which sales department are you assigned to?", // Optional. Non graded questions asked before Exam is started.
    "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",  // Optional. The tracking IDs sent your system.  Integration Options.
    "ip_address": "192.168.0.1"
  },
  "questions": [ // Questions, User Responses and Category Results are only included if selected in your Webhook settings.
    {
      "question_id": 3542854,
      "question_type": "multiplechoice",
      "category_id": 1,
      "points_available": 2,     // Points can be to one decimal place.
      "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,        // Points can be to one decimal place.
      "user_response": "C",      // If Question was not answered, "user_response" key will not exist.
      "result": "correct",       // Possible Values: correct / partial_correct / incorrect / requires_grading / unanswered.
      "feedback": "Great, and remember, never use oil on Skin burns!"  // Optional: Question Feedback. If no Question Feedback is set, "feedback" key will not exist
    },
    {
      "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 a 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": "",   // "custom_feedback" is only used for Essay Questions. Administrators can add when grading Essay Questions.
      "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 results from Questions are only included if selected in your Webhook settings.
    {
      "category_id": 1,
      "name": "Health and Safety",
      "percentage": 66.7,      // Percentage can be to one decimal place.
      "points_available": 6,   // Points can be to one decimal place.
      "points_scored": 4       // Points can be to one decimal place.
    },
    {
      "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
    }
  ]
}

JSON Keys explained

Also see comments within JSON examples above.


payload_type:     // Verify if results where taken under ClassMarkers' "Group" or "Link" options. Learn more.
payload_status:   // live: Actual Results. verify: Sample Results sent when setting up your Webhook from within your account.


                  ## Group results: Identify unique individuals results using a combination of:

user_id:          // Each registered User has a unique ID user_id in ClassMarker
test_id:          // Each Test has a unique ID in ClassMarker
group_id:         // Each Group has a unique ID in ClassMarker
time_started:     // Because Registered users can Retake their Tests (if allowed) use the time_started Timestamp
                     to understand if this is an Updated Test result (Regraded) OR a New Test result.
                     E.G: time_started Timestamps are never updated, so if you receive the same timestamp twice
                          for the same Users Test, then these results must have been resent manually.


		  ## Link results: Identify unique individuals results using: link_result_id

link_result_id:   // Each Link results has a unique Link Result ID
view_results_url: // Unique URL per Test result for viewing formatted Test results back on ClassMarker (without being logged into a ClassMarker account). An Access control password is required for viewing access. Set this password on your My account page.


		  ## Questions: Questions work the same for both Groups and Links

question_id:      // Each Question has a unique ID in ClassMarker
question_type:    // Available Question types include:
                  // multiplechoice: Covers "Multiple choice" and "Multiple response" Questions. Graded by ClassMarker.
                  // truefalse: "True / False" Question. Graded by ClassMarker.
                  // freetext: "Free text" Questions. Users type a short answer. Graded by ClassMarker.
                  // matching: "Matching" Questions. User select a Match for each Clue. Graded by ClassMarker.
                  // essay: "Essay" Questions. User can write a long answer. Graded by account Administrator. Learn more.
                  // grammar: "Grammar" Questions. Check punctuation and Grammer. Graded by ClassMarker.

category_id:      // Each Question can be categorized. Each Category created has a unique ID.
points_available: // Total points question is worth. Points can be to one decimal place.
question:         // Question text. Can contain HTML formatting.
options:          // Contains answer options for Multiple choice/response, Free Text and Matching Questions. Can contain HTML formatting.
points_scored:    // Points given for the answer. Points can be to one decimal place.
user_response:    // Test takers' selected option OR typed answer. If no answer was given, no "user_response" key will exist.
result:           // If answer was: correct / partial_correct / incorrect / requires_grading / unanswered.
feedback:         // Feedback for this answer. If no Feedback exists for a Question, "feedback" key will exist. Can contain HTML formatting.
custom_feedback:  // "custom_feedback" is only used for Essay Questions. Administrators can add when grading Essay Questions.


                  ## Category results: Category results derived from Question points.

category_id:      // Each Question can be categorized. Each Category created has a unique ID.
name:             // Name given to Category by Administrator.
percentage:       // Percentage can be to one decimal place.
points_available: // Points can be to one decimal place.
points_scored:    // Points can be to one decimal place.

Retrieve Quiz Results via Webhooks

Webhook Code Examples

You can use these code examples to receive and verify Webhook Payloads from ClassMarker.

  1. Add the code below to a public accessible URL on your server (which you should keep a secret)
  2. Create a Webhook in ClassMarker via your My Account / Webhooks page
  3. Add your public accessible URL to the Endpoint box
  4. Select the Save button
  5. Copy your WEBHOOK SECRET PHRASE from ClassMarker and replace it in your app. SEE: YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE
  6. Back on your webhooks page, Check the 'Save and Verify' checkbox
  7. Select the Save button
  8. You will then see the header response your Web servers sent back such as 200 (or 404 if your Endpoint URL is incorrect)
  9. With a 2XX response, your Webhook will then be Active and can now be selected when assigning Tests to be taken.
  10. How Your Server Should Respond
Note
  • HTTP signature header sent as: X-Classmarker-Hmac-Sha256

Github Code Examples Also Available

Quiz maker Webhooks receive quiz results using PHP
Quiz maker Webhooks receive quiz results using Ruby
Quiz maker Webhooks receive quiz results using Python
Quiz maker Webhooks receive quiz results using Go
Quiz maker Webhooks receive quiz results using Node
Quiz maker Webhooks receive quiz results using Java
Quiz maker Webhooks receive quiz results using C#

PHP Webhook code example for quiz maker to receive quiz results in real time
Ruby Webhook code example for quiz maker to receive quiz results in real time
Go Webhook code example for quiz maker to receive quiz results in real time
Python Webhook code example for quiz maker to receive quiz results in real time
C# Webhook code example for quiz maker to receive quiz results in real time
Java Webhook code example for quiz maker to receive quiz results in real time

PHP Ruby Python Go Node C# Java

// PHP webhooks code example by ClassMarker.com

// See: https://www.classmarker.com/online-testing/api/webhooks/


// You are given a uniquе secret codе when creating a Wеbhook.
define('CLASSMARKER_WEBHOOK_SECRET', 'YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE');

// Verification function.
function verify_classmarker_webhook($json_data, $header_hmac_signature)
{

	$calculated_signature = base64_encode(hash_hmac('sha256', $json_data, CLASSMARKER_WEBHOOK_SECRET, true));
	return ($header_hmac_signature == $calculated_signature);

}


// ClassMarker sent signaturе to chеck against.
$header_hmac_signature = $_SERVER['HTTP_X_CLASSMARKER_HMAC_SHA256'];

// ClassMarker JSON payload (The Tеst Results).
$json_string_payload = file_get_contents('php://input');

// Call vеrification function.
$verified = verify_classmarker_webhook($json_string_payload, $header_hmac_signature);

// Add JSON payload to array for rеferencing elements.
$array_payload = json_decode($json_string_payload, true);

if ($verified)
{

	// Notify ClassMarker you have recеived the Wеbhook.
	http_response_code(200);

	// Save results in your databasе.
	// Important: Do not use a script that will takе a long time to respond.

} else  {

	// Something went wrong.
	http_response_code(400);

}

// DEBUGGING: Log results directly to a text file to chеck we are receiving them.
define('DEBUGGING', false);

if (DEBUGGING)
{
    // Open file in same dirеctory to write test results JSON to.
    $file = fopen("log.txt", "w");

    // Note: Each webhook requеst will overwrite the last logged entry.
    fwrite($file, date("D jS M Y g:ia", time() ) . "\n\n" . $json_string_payload);

    // Close file handler.
    fclose($file);

}
# RUBY webhooks code example by ClassMarker.com

# See: https://www.classmarker.com/online-testing/api/webhooks/


require 'base64'
require 'openssl'

class ClassmarkerController < ApplicationController

  # no nеed for CSRF
  skip_before_action :verify_authenticity_token

  before_action :verify_hmac_signature
  before_action :verify_payload_json

  def webhook
    save_webhook_data request.raw_post

    # Notify ClassMarker you have recеived the Wеbhook.
    head :ok
  end

  private

  class InvalidPayloadError < StandardError
    def initialize
      super 'Payload must be valid JSON'
    end
  end

  class InvalidHMACError < StandardError
    def initialize
      super 'Invalid HMAC signature'
    end
  end

  def verify_hmac_signature
    raise InvalidHMACError unless hmac_header_valid?
  end

  def verify_payload_json
    raise InvalidPayloadError unless payload_json?
  end

  def save_webhook_data(data)
    directory = Rails.root.join('webhook_data')
    unless File.exist? directory
      FileUtils.mkdir_p(directory)
    end

    filename = File.join directory, timestamped_filename

    File.open(filename, 'w') do |file|
      file.puts data
    end
  end

  def hmac_header_valid?
    headerVal = request.headers['HTTP_X_CLASSMARKER_HMAC_SHA256']
    return false unless headerVal.present?

    expected = headerVal.split(/,/).first
    actual = calculate_signature(request.raw_post)

    ActiveSupport::SecurityUtils.secure_compare(actual, expected)
  end


  def calculate_signature(data)
    secret = 'YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE'

    digest = OpenSSL::Digest.new('sha256')
    calculated_hmac = Base64.encode64(OpenSSL::HMAC.digest(digest, secret, data)).strip
  end

  def payload_json?
    JSON.parse(request.raw_post)
    true
  rescue
    false
  end

  def timestamped_filename(extension = '.json')
    Time.now.strftime('%Y-%m-%d_%H-%M-%S') + extension
  end
end
# PYTHON wеbhooks code example by ClassMarker.com

# See: https://www.classmarker.com/online-testing/api/webhooks/


import json
import hmac
import hashlib
import base64
import os
from datetime import datetime

from django.shortcuts import render
from django.http import HttpResponse
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt

def index(request):
    return HttpResponse("Hello, world.")

def verify_payload(payload, header_hmac_signature):

    webhook_secret = "YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE"
    dig = hmac.new(webhook_secret.encode(), msg=payload, digestmod=hashlib.sha256).digest()
    calculated_signature = base64.b64encode(dig).decode().encode('ascii','ignore')

    return hmac.compare_digest(calculated_signature, header_hmac_signature)

@csrf_exempt
def webhook_view(request):
    hmac_header = request.META.get('HTTP_X_CLASSMARKER_HMAC_SHA256')

    if verify_payload(request.body, hmac_header):
        json_data = json.loads(request.body)
        filename = 'json_data_{}.json'.format(datetime.now())
        with open(filename, 'w') as fileobj:
            json.dump(json_data, fileobj, indent=4)

        # Notify ClassMarker you have rеceived the Wеbhook.
        return HttpResponse("OK", status=200)
    else:
        return HttpResponse("Fail", status=400)
// GO wеbhooks code example by ClassMarker.com

// See: https://www.classmarker.com/online-testing/api/webhooks/


package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"io/ioutil"
	"log"
	"net/http"
	"fmt"
)

// You are given a unique sеcret code when creating a wеbhook.
var ClassmarkerWebhookSecret = "YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE"

func WebHook(w http.ResponseWriter, r *http.Request) {

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Println("Error reading body: ", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	jsonData := string(body)

	// ClassMarker sеnt signature to chеck against
	headerHmacSignature := r.Header.Get("X-Classmarker-Hmac-Sha256")
	verified := VerifyClassmarkerWebhook(jsonData, headerHmacSignature)

	if verified {
		// Save results in your database.
		// Important: Do not use a script that will take a long time to respond.

		// Notify ClassMarker you have recеived the Wеbhook.
		w.WriteHeader(http.StatusOK)
	} else {
		w.WriteHeader(http.StatusBadRequest)
	}

}

func VerifyClassmarkerWebhook(jsonData string, headerHmacSignature string) bool {
	calculatedSignature := ComputeHmac256(jsonData, ClassmarkerWebhookSecret)
	return headerHmacSignature == calculatedSignature
}

func ComputeHmac256(message string, secret string) string {
	key := []byte(secret)
	h := hmac.New(sha256.New, key)
	h.Write([]byte(message))
	return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

func main() {
    http.HandleFunc("/webhook", WebHook)
    http.ListenAndServe(":8080", nil)
}
// C# wеbhooks code example by ClassMarker.com

// See: https://www.classmarker.com/online-testing/api/webhooks/


using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Web.Http;

namespace cSharp.Controllers
{
    public class WebHookController : ApiController
    {
        private string SECRET_KEY = "YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE";

        [HttpPost]
        [Route("api/webhook")]
        public async Task<IHttpActionResult> ProcessWebHook()
        {
            var payloadJson = await Request.Content.ReadAsStringAsync();

            if (!await PayloadSignatureMatchesHeader(payloadJson))
            {
                return Unauthorized();
            }

            if (payloadJson == null)
            {
                return BadRequest("Invalid object");
            }

            SaveToFile(payloadJson);

            // Notify ClassMarker you have rеceived the Wеbhook.
            return Ok();

        }

        private void SaveToFile(string payloadJson)
        {
            var pathFile = @"c:/temp/log.txt";

            File.AppendAllText(pathFile, payloadJson);
        }


        private async Task<bool> PayloadSignatureMatchesHeader(string payloadJson)
        {
            var hashFromHeader = Request.Headers.GetValues("X-Classmarker-Hmac-Sha256").FirstOrDefault();
            var hashFromPayload = GenerateSHA256FromPayload(payloadJson);
            return hashFromPayload == hashFromHeader;
        }


        private string GenerateSHA256FromPayload(string payloadJson)
        {
            var encoding = new System.Text.ASCIIEncoding();
            var keyByte = encoding.GetBytes(SECRET_KEY);
            var payloadBytes = encoding.GetBytes(payloadJson);

            using (var hmacsha256 = new HMACSHA256(keyByte))
            {
                var hashmessage = hmacsha256.ComputeHash(payloadBytes);
                return Convert.ToBase64String(hashmessage);
            }
        }

    }
}
// JAVA wеbhooks code еxample by ClassMarker.com

// See: https://www.classmarker.com/online-testing/api/webhooks/


package com.classmarker.webhook;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import java.util.Objects;
import java.util.Enumeration;
import java.util.stream.Collectors;
import java.util.Base64;

@WebServlet("/webhook")
public class WebhookServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        // You are given a uniquе secret code when crеating a Wеbhook.
        String classmarkerSecretPhrase = "YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE";

        String jsonStringPayload = httpServletRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
        String headerHmacSignature = httpServletRequest.getHeader("x-classmarker-hmac-sha256");

        boolean isVerifiedSuccessfully = false;

        try {
            isVerifiedSuccessfully = verifyWebhook(jsonStringPayload, headerHmacSignature, classmarkerSecretPhrase);
        } catch (InvalidKeyException | NoSuchAlgorithmException exception) {
            // handle еxception hеre			 
        }

        if (isVerifiedSuccessfully) {
            saveToFile("json_data_" + new Date() + ".json", jsonStringPayload);

            // Notify ClassMarker you have rеceived the Wеbhook.
            httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        }
        else{
            httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }

    }

    private boolean verifyWebhook(String jsonData, String headerHmacSignature, String classmarkerSecretPhrase) throws InvalidKeyException, NoSuchAlgorithmException {
        byte[] hashHMacBytes = hashHMac(jsonData, classmarkerSecretPhrase);
        String calculatedSignature = Base64.getEncoder().encodeToString(hashHMacBytes);

        return Objects.equals(headerHmacSignature, calculatedSignature);
    }

    private byte[] hashHMac(String sourceString, String key) throws InvalidKeyException, NoSuchAlgorithmException {
        String HMAC_SHA256 = "HmacSHA256";
        Mac sha512HMAC = Mac.getInstance(HMAC_SHA256);

        byte[] keyBytes = key.getBytes(StandardCharsets.US_ASCII);

        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, HMAC_SHA256);

        sha512HMAC.init(secretKeySpec);

        return sha512HMAC.doFinal(sourceString.getBytes(StandardCharsets.US_ASCII));
    }

    private void saveToFile(String fileName, String content) throws IOException {
        File file = new File(fileName);

        FileOutputStream fileOutputStream = new FileOutputStream(file);

        // If filе doesn't еxists, then crеate it.
        if (!file.exists()) {
            file.createNewFile();
        }

        fileOutputStream.write(content.getBytes());
        fileOutputStream.flush();
        fileOutputStream.close();
    }
}
// Node.js wеbhooks code еxample by ClassMarker.com

// See: https://www.classmarker.com/online-testing/api/webhook

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const forge = require('node-forge');


var rawBodySaver = function (req, res, buf, encoding) {
  if (buf && buf.length) {
    req.rawBody = buf.toString(encoding || 'utf8');
  }
}

app.use(bodyParser.json({ verify: rawBodySaver }));
app.use(bodyParser.urlencoded({ verify: rawBodySaver, extended: true }));
app.use(bodyParser.raw({ verify: rawBodySaver, type: '*/*' }));


app.listen(8080, function () {
  console.log('Example app listening on port 8080!')
});


app.post('/webhook', function (req, res) {

    var headerHmacSignature = req.get("X-Classmarker-Hmac-Sha256");

    // You are given a unique secret code when creating a Webhook.
    var secret = 'YOUR_CLASSMARKER_WEBHOOK_SECRET_PHRASE';

    var verified = verifyData(req.rawBody,headerHmacSignature,secret);

    if(verified){
        // Save results in your database.
        // Important: Do not use a script that will take a long timе to respond.

        // Notify ClassMarker you have recеived the Wеbhook.
        res.sendStatus(200);
    } else {
        res.sendStatus(400);
    }

});

var verifyData = function(rawBody,headerHmacSignature, secret)
{
    var jsonHmac = computeHmac(rawBody, secret);
    return jsonHmac == headerHmacSignature;
};

var computeHmac = function(rawBody, secret){
    var hmac = forge.hmac.create();
    hmac.start('sha256', secret);
    var jsonString = rawBody;
    var jsonBytes = new Buffer(jsonString, 'ascii');
    hmac.update(jsonBytes);
    return forge.util.encode64(hmac.digest().bytes());
};

Database Scheme

Because our API and Webhooks both handle the same Test results data (Questions, Responses and Category results not included), you can use the same database table schema design to store your results.

Even if designing your own tables, the table schema included in the Example code zip will give you a reference of table column types.

Go to our API Documentation and download the Example code zip file for more details.

Sending Sample Results

You do not need Results saved to test out your Webhook script.

You can send sample Test results directly from ClassMarker when you Create or Edit your Webhook in ClassMarker.

Learn how to setup a Webhook for your Account in our User Manual.

Testing code on your Localhost? You may find https://ngrok.com/ useful to setup a secure URL to your localhost server.

Sending Results in Real Time

Important: You first need to assign the Webhook to your Group and/or Link when assigning your Test.

If a Test is already assigned: Go to your 'Tests' page and Click 'Settings' next to the Group or Link.

Online exam wehooks settings

You can use the same Webhook across all your Groups and Link, OR, create multiple webhooks for separate Groups and Links.

When you have a Webhook assigned to a Test, when the Test in completed, the results will be sent in Real Time to your Webhook Endpoint Url.

Send Existing Saved Results Manually

If you have previously saved results, that have not yet been sent to your Webhook, you can send those results via your Webhook manually from individual Test results pages.

Online exam wehooks settings

Note: A Webhook must be assigned to your Test to manually send results with. This can be done before or after the Test has been taken.

Alternatively see our API Documentation to request multiple results at once.

View Results URL

Give access to colleagues who need to review Formatted Test Results (including Questions and Answers) without them needing a ClassMarker account.

See parameter: view_results_url

Including this URL is optional and needs to be turned on in the Webhook settings page. Each URL is unique and unguessable.

IMPORTANT: Allow Viewing Access to: view_results_url

  1. Go to your My account page in ClassMarker
  2. Scroll down to the 'API' setting box
  3. Select your control settings
  4. Save: Select 'Update Access Settings' button

Access Control options include:

  • Password Protection
  • Expire URL after X Days or Months
  • Hide Test Taker Details for Anonymous grading
  • Allow Grading
  • Allow Submitting New Graded Results via Webhooks

Download our: Quiz API Integration Guide for your Web developers.

IMPORTANT: Receiving "Updated" Results

After Exams are completed, (E.G. After you receive the original Results Payload), the results may:
1. Require grading (Essay Question types only)
2. Be re-graded manually (Administrators may want to change points for an answer)
In these cases, results can then be manually resent via the Webhook (performed from the results pages after grading) to update your system with the upgraded score.

So we recommend you build your script 'that receives Webhooks', to first check if the result already exists and update it rather then insert it again.

Use these fields below to see if a "Test result" already exists in your database, then you can update the result rather than duplicate it.

-- Unique Group Results Fields --
Note: The same Test can be assigned to multiple Groups, so you need to check the Group ID along with Test ID, E.G. The same User (ID) could take the same Test (ID) under multiple Groups.


                ## These 4 values together will be unique per test, per user

user_id:        // Each registered User has a unique ID user_id in ClassMarker
test_id:        // Each Test has a unique ID in ClassMarker
group_id:       // Each Group has a unique ID in ClassMarker
time_started:   // Because Registered users can Retake their Tests (if allowed) use the time_started Timestamp
		   to understand if this is an Updated Test result (Regraded) OR a New Test result.
		   E.G: time_started Timestamps are never updated, so if you receive the same timestamp twice
			for the same Users Test, then these results must have been resent manually.

-- Unique Link Results Fields --
Each Link results has a unique Link Result ID.


                 ## Each Link results has a unique Link Result ID

link_result_id:  // Simply use the link_result_id to check if this is
                    an Updated Test result or a New Test result 

HTML formatting in Exam Questions & Feedback

Questions (including Answer options) and Feedback fields can contain HTML formatting as set by Administrators on our Wysiwyg editors (EG: 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 TAG: <br>?", this will display the break TAG, not create a line break.

Important: Because a 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.

Version control

Ensure you reference the JSON keys by name, to ensure any additional KEYs added in the future will not break your scripts.

Future upgrades may add, but not change or remove JSON Keys. No 'structure' changes are expected.

ClassMarker will notify customers by email with 30 days notice if new versions require any changes, but this is not expected.

Failed Payload Retry Rules

We understand getting results back to your system in real time can be critical, that's why ClassMarker will retry 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, results will still be delivered soon after.

Failed Webhook Scenario

  1. When a Quiz is finished, the First Webhook attempt is triggered.
  2. If the Webhook fails (a 2XX response is not received), it is tried a second time within minutes.
  3. If the Webhook fails the second time, ClassMarker will then reattempt the single Quiz result webhook once per hour for 72 hours.
  4. If your website fails to successfully respond to Webhooks 1,000 times 'in a row' (this includes retry attempts for all results it is trying to send), the Webhook will be set to inactive and will 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 1,000 times 'in a row', will be set as inactive. Your other Webhooks will continue to run as normal.

Note: When a Webhook is inactive, Test results are not sent and are not enabled for a retry, you will need to send those Test results using your Webhook manually from the results pages instead.

Note: If your Webhook has, for example, 7 failed requests in a row, then it sends a successful 2XX response, the failed requests count will reset from 7 back to 0.

Email alerts
ClassMarker will send you 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 Verifiy Webhook Payloads

When a Webhook Payload is delivered to your system, you can verify it came from ClassMarker by using the Secret Phrase you will receive when creating your Webhook. Only you and ClassMarker will know your unique 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 use to verify on your end.

To verify the Payload came from ClassMarker, encrypt your JSON payload with ClassMarkers Webhook secret.

// PHP Verify Webhook code example by ClassMarker.com


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

View: Full code examples and more languages above

For other code examples in your required language, search "Examples of creating base64 hashes using HMAC SHA256 in different languages"

https://webhook.site for Debugging (Third party website)

You can also use services such as https://webhook.site to view Test results sent via your Webhook. https://webhook.site which will give you a unique URL to use as your temporary Endpoint URL.

After you add the https://webhook.site URL to your Endpoint URL (when creating or editing your Webhook), and you can view the 'Sample Test results' ClassMarker sends when saving and verifying your Webhook.

  1. Go to https://webhook.site to get your unique URL
  2. Add the https://webhook.site URL to your Endpoint URL (when creating or editing your Webhook).
  3. Check the 'Save and Verify' checkbox
  4. Select the Save button
  5. You can now view the 'Sample Test results' in https://webhook.site

payload_status: Verify VS Live

payload_status will read "payload_status":"verify" when using the Verification option from your Edit webhooks page.

payload_status will read "payload_status":"live" when real Test results are being sent.

See: JSON Keys explained for more information.

Troubleshoot: Check your System (Third party website)

If you are having any issues receiving results, you can check your system responses with http://onlinecurl.com/

This may highlight for example, a SSL certificate issue, or another cause your system is not functionality correctly.

How Your Server Should Respond

Your server should respond with a 200 or any 2XX HTTP RESPONSE CODE, no body response is required.

If a 2XX response is not received, the Webhook will be Retried.

To verify the Payload came from ClassMarker, encrypt your JSON payload with your unique Webhook secret.
See: How to Verifiy Webhook Payloads

// PHP Response code example by ClassMarker.com


http_response_code(200);

View: Full code examples and more languages above

For other code examples in your required language, search "Examples of creating base64 hashes using HMAC SHA256 in different languages"

What are Groups & Links?

Groups & Links explained

ClassMarker administrators have the opportunity to assign (distribute) exams in two ways:

  1. Groups
    By registering users into groups (handy for school classrooms or work place employee groups/departments).
    Note: The same Exam can be assigned to multiple groups.
  2. Links
    By creating a unique direct link and distributing this link either via email or by adding/embedding it in a web page (no pre registration required for Test takers).
    Note: The same Exam can have multiple direct links to help keep results separated for different groups of users, or use separate settings such as different time limits.

Learn More: Giving access to exams.

When retrieving JSON Exam results from ClassMarker via Webhooks, you can tell if it is a Group or Link result because there will be either a Group or Link element in the JSON payload. See examples above.

  1. Groups (Registered users)

    Referenced as: Groups

    Each Group can have multiple Exams assigned to them. Group members log into ClassMarker.com to take Exams assigned to their Group.
    Registered users can also be registered in multiple Groups, which allows them to change between Groups when they are logged in and take any Exams available to their Groups.

  2. Links (Non registered Users)

    Referenced as: Links

    Each Link can only contain one Exam and has a unique link (quiz_id parameter) to access the Exam. (Public and private access options are available when administering Exams via Direct link).

Pass Data to ClassMarker (Pre Test)

When testing with our Links option, you can pass your Test takers data such as their Name, Email or their user_id from your system.

This data is saved in ClassMarker and returned with respective Webhook payloads.

This makes passing Test takers tracking details to ClassMarker easy to reference exam results back in your system.

More about Integrating your online testing

Third Party Integrations

You can also use ClassMarker Webhooks to send Exam results directly to third party services.

https://zapier.com is a service that can receive Webhooks from ClassMarker, and send Test results to a third party service of your choice.

For example: You can have a Test takers "Name, Email address and Test score" added to a Google Spreadsheet the moment an exam is finished for Lead Generation, or have your customers Test scores updated in your CRM system or Online Recruiting Portal.

  1. In your Zappier account, create a zap of your choice using "Webhooks by Zappier" option and the third party service you which to integrate Test scores with.
  2. Copy your "unique Webhook URL" from Zappier for use in the next step
  3. Back in ClassMarker, create a Webhook and use the Zappier "unique Webhook URL" from the step above and the Endpoint URL

Once you have set up your "ZAP" on Zappier and "Webhook" on ClassMarker, you can then assign your Webhook to a Group or Link via their settings and your integration will start working.

Zappier Help:

  1. See: Zappier Webhook Introduction Video
  2. Learn how to Access Nested Elements in Zappier from ClassMarker Webhook data
  3. View the many services you can integrate your Exam results into with: Webhooks with Zappier

Share Your Find With Colleagues

Visit ClassMarker on Facebook

Forgot password? / Register free