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 allow your Web developers to have Test results securely delivered to your systems/website in Real Time.
Example: On completion, Test results are delivered instantly.
View WebHooks Documentation below.API keys allow your Web developers to have your systems/website securely request & retrieve recent Test results Periodically.
Example: Requests for new Test results can be hourly.
Go to: API DocumentationIN 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.
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 } ] }
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.
You can use these code examples to receive and verify Webhook Payloads from ClassMarker.
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 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()); };
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.
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.
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.
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.
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.
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.
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
Access Control options include:
Download our: Quiz API Integration Guide for your Web developers.
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
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.
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.
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.
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.
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"
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.
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.
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.
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"
ClassMarker administrators have the opportunity to assign (distribute) exams in two ways:
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.
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
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.
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: