Webhooks Integration Guide
To enable webhooks to be sent from your Scorecard go to Integrate > Webhooks and click Connect to add the URL that you'd like to receive webhooks too.
Note: You must provide a secure (https) URL that can receive POST requests
Once you have provided your secure URL to receive post requests you will be able to specify a secret key and which events you'd like to receive.
Events
There are 4 possible events you may receive as a webook:
Quiz Started (QUIZ_STARTED)
Quiz Finished (QUIZ_FINISHED)
Lead Details Updated (LEAD_DETAILS_UPDATED)
Lead Signed Up (LEAD_SIGNED_UP)
Webhook Payload Format
General structure
{ "event_name" : "Event name", "data": {} // - Response data }
Example QUIZ_STARTED webhook
{ "event_name" : "QUIZ_STARTED", "data" : { "id": "1e11c712-c4f7-48e9-8bc7-7d86ea273a6f", "status": "Started", "key": "64b00a39ce128979457258", "first_name": null, "last_name": null, "email": "example@example.com", "started_at": "2023-07-06T08:12:09.000Z", "opt_in": true, "opt_in_detail": "Implied", "utm_source": null, "utm_campaign": null, "utm_medium": null, "utm_term": null, "utm_content": null, "ip_address": "172.23.0.1", "lead_form_questions": [ //- possible if we added fields to lead form. { "id": "01a725d2-bd2c-4aa0-a799-6d6fc8e7ac9d", "question": "Question", "answers": [ { "answer": "Answer" } ] }, ], "quiz_questions": [], "abandon_email_url": "https://example.scoreapp.com/continue/64a6775915bf6092267456" } };
Example QUIZ_FINISHED webhook
{ "event_name" : "QUIZ_FINISHED", "data" : { "id": "1e11c712-c4f7-48e9-8bc7-7d86ea273a6f", "status": "Finished", "key": "64b00a39ce128979457258", "first_name": null, "last_name": null, "email": "example@example.com", "started_at": "2023-07-06T08:25:25.000Z", "opt_in": true, "opt_in_detail": "Implied", "utm_source": null, "utm_campaign": null, "utm_medium": null, "utm_term": null, "utm_content": null, "ip_address": "172.23.0.1", "lead_form_questions": [ //- possible if we added fields to lead form. { "id": "01a725d2-bd2c-4aa0-a799-6d6fc8e7ac9d", "question": "Question", "answers": [ { "answer": "Answer" } ] }, ], "quiz_questions": [ { "id": "0f9d99a8-1835-46d7-bb1f-86b579195797", "question": "Question 1", "answers": [ { "answer": "No" } ] }, { "id": "d9cadc83-50df-442c-aeb2-a0878e3e7fb7", "question": "Question 2", "answers": [ { "answer": "5" } ] }, { "id": "ea795cc9-e437-4734-a85a-34837dccbb2d", "question": "Question 3", "answers": [ { "answer": "Answer 1" }, { "answer": "Answer 2" }, { "answer": "Answer 3" } ] }, ], "finished_at": "2023-07-06T08:25:29.000Z", "report": "https://example.scoreapp.com/results/64a67a753fbb6186785302/pdf", "lowest_category": { "id": "433cad86-ce58-4755-9b97-16cbbea42b15", "title": "Category 2" }, "highest_category": { "id": "3fc9bef7-c890-4ec7-9414-f13929ac4e38", "title": "Category 1" }, "total_score": { "actual": "67.00", "percent": 54, "denominator10": 5.36, "tier": "medium" }, "category_scores": [ { "actual": "56.00", "percent": 50, "denominator10": 4.96, "tier": "medium", "category": { "id": "e6b19afd-891c-41cc-ab44-c7e2e8485acd", "title": "Category 1" } }, { "actual": "4.00", "percent": 33, "denominator10": 3.33, "tier": "low", "category": { "id": "ff35527a-2cb8-4ed0-b142-5bad8ac14818", "title": "Category 2" } }, ], "result_url": "https://example.scoreapp.com/results/64a67a753fbb6186785302", "audiences": [ { "id": "30672ea7-8515-46bd-bde1-09b8e0986873", "name": "Audience 1" }, { "id": "278e64c9-71e4-4cb1-bc70-009e33357336", "name": "Audience 2" } } };
Example LEAD_SIGNED_UP webhook
{ "event_name" : "LEAD_SIGNED_UP", "data" : { "id": "1e11c712-c4f7-48e9-8bc7-7d86ea273a6f", "status": "Started", "key": "64b00a39ce128979457258", "first_name": null, "last_name": null, "email": "example@example.com", "started_at": "2023-07-06T08:12:09.000Z", "opt_in": true, "opt_in_detail": "Implied", "utm_source": null, "utm_campaign": null, "utm_medium": null, "utm_term": null, "utm_content": null, "ip_address": "172.23.0.1", "lead_form_questions": [ //- possible if we added fields to lead form. { "id": "01a725d2-bd2c-4aa0-a799-6d6fc8e7ac9d", "question": "Question", "answers": [ { "answer": "Answer" } ] }, ], "quiz_questions": [], "abandon_email_url": "https://example.scoreapp.com/continue/64a6775915bf6092267456" } };
Example LEAD_DETAILS_UPDATED webhook
{ "event_name" : "LEAD_DETAILS_UPDATED", "data" : { "id": "1e11c712-c4f7-48e9-8bc7-7d86ea273a6f", "status": "Finished", "key": "64b00a39ce128979457258", "first_name": null, "last_name": null, "email": "example.changed@example.com", "started_at": "2023-07-06T08:25:25.000Z", "opt_in": true, "opt_in_detail": "Implied", "utm_source": null, "utm_campaign": null, "utm_medium": null, "utm_term": null, "utm_content": null, "ip_address": "172.23.0.1", "lead_form_questions": [ //- possible if we added fields to lead form. { "id": "01a725d2-bd2c-4aa0-a799-6d6fc8e7ac9d", "question": "Question", "answers": [ { "answer": "Answer changed" } ] }, ], "quiz_questions": [ { "id": "0f9d99a8-1835-46d7-bb1f-86b579195797", "question": "Question 1", "answers": [ { "answer": "No" } ] }, { "id": "d9cadc83-50df-442c-aeb2-a0878e3e7fb7", "question": "Question 2", "answers": [ { "answer": "5" } ] }, { "id": "ea795cc9-e437-4734-a85a-34837dccbb2d", "question": "Question 3", "answers": [ { "answer": "Answer 1" }, { "answer": "Answer 2" }, { "answer": "Answer 3" } ] }, ], "finished_at": "2023-07-06T08:25:29.000Z", "report": "https://example.scoreapp.com/results/64a67a753fbb6186785302/pdf", "lowest_category": { "id": "433cad86-ce58-4755-9b97-16cbbea42b15", "title": "Category 2" }, "highest_category": { "id": "3fc9bef7-c890-4ec7-9414-f13929ac4e38", "title": "Category 1" }, "total_score": { "actual": "67.00", "percent": 54, "denominator10": 5.36, "tier": "medium" }, "category_scores": [ { "actual": "56.00", "percent": 50, "denominator10": 4.96, "tier": "medium", "category": { "id": "e6b19afd-891c-41cc-ab44-c7e2e8485acd", "title": "Category 1" } }, { "actual": "4.00", "percent": 33, "denominator10": 3.33, "tier": "low", "category": { "id": "ff35527a-2cb8-4ed0-b142-5bad8ac14818", "title": "Category 2" } }, ], "result_url": "https://example.scoreapp.com/results/64a67a753fbb6186785302", "audiences": [ { "id": "30672ea7-8515-46bd-bde1-09b8e0986873", "name": "Audience 1" }, { "id": "278e64c9-71e4-4cb1-bc70-009e33357336", "name": "Audience 2" } } };
Failures and Retries
If the URL to which we send a webhook fails to send a response with a 2xx
status code we will consider the call as failed. The call will also be considered failed if the remote app doesn't respond within 10 seconds
If the call fails we will attempt to resend the webhook up to a maximum of 5 times.
Optional Webhook Header / Secret Key (Recommended)
If you provide an optional “WEBHOOK SECRET KEY”, a header (Scoreapp-Signature
) will be included with each webhook request containing a hashed string of the request data using the secret key as the signing secret allowing you to securely validate the authenticity of the request.
To validate the request you will need to recreate the hashed string using the same secret key and compare the one sent in the Scoreapp-Signature header with the one you recreate. If they match then the request is valid and you can continue processing the webhook data
The following are examples of handling the Scoreapp-Signature header in various coding languages and frameworks.
PHP
public function webhookHandler(Request $request) { $requestContent = file_get_contents('php://input'); $signature = isset($_SERVER['HTTP_SCOREAPP_SIGNATURE']) ? $_SERVER['HTTP_SCOREAPP_SIGNATURE'] : ''; $signingSecret = 'Your webhook secret key'; $computedSignature = hash_hmac('sha256', $requestContent, $signingSecret); if (!hash_equals($signature, $computedSignature)) { header('HTTP/1.1 401 Unauthorized'); echo 'Unauthorized'; exit; } // your code goes here header('HTTP/1.1 200 OK'); echo 'Webhook handled'; }
Python (using Flask)
from flask import Flask, request, abort import hashlib import hmac app = Flask(__name__) @app.route('/webhook', methods=['POST']) def webhook_handler(): signature = request.headers.get('Scoreapp-Signature') signing_secret = 'Your webhook secret key' computed_signature = hmac.new(signing_secret.encode(), request.data, hashlib.sha256).hexdigest() if not hmac.compare_digest(signature, computed_signature): abort(401) # your code goes here return 'Webhook handled', 200 if __name__ == '__main__': app.run(debug=True)
Node.js (using Express)
const express = require('express'); const crypto = require('crypto'); const app = express(); app.use(express.json()); app.post('/webhook', (req, res) => { const signature = req.headers['scoreapp-signature']; const signingSecret = 'Your webhook secret key'; const computedSignature = crypto .createHmac('sha256', signingSecret) .update(JSON.stringify(req.body)) .digest('hex'); if (computedSignature !== signature) { return res.status(401).send('Unauthorized'); } // your code goes here res.status(200).send('Webhook handled'); }); app.listen(3000, () => { console.log('Server is running on port 3000'); });
Java (using Spark Java)
import static spark.Spark.*; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class WebhookHandler { public static void main(String[] args) { post("/webhook", (request, response) -> { String signature = request.headers("Scoreapp-Signature"); String signingSecret = "Your webhook secret key"; String requestBody = request.body(); Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(signingSecret.getBytes(), "HmacSHA256"); sha256_HMAC.init(secret_key); String computedSignature = Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(requestBody.getBytes())); if (!computedSignature.equals(signature)) { response.status(401); return "Unauthorized"; } // your code goes here response.status(200); return "Webhook handled"; }); } }