Webhooks

Overview

Webhooks are at the centre of how Vantage API is able to keep you connected to the kit throughout the fulfilment process. This guide will outline how we use webhooks and how you can integrate with them.

Webhook Verification

  • Diagnostics API uses HMAC-SHA256 with your unique signing secret to generate webhook signatures

  • Below is some example code on how to verify a webhook secret

import hmac
import hashlib
import time
from flask import Flask, request, abort

app = Flask(__name__)

SIGNING_SECRET = "your_signing_secret_here"
TIMESTAMP_TOLERANCE = 300000  # 5 minutes in milliseconds

def verify_webhook(request):
    # 1. Get signature header
    signature_header = request.headers.get('X-Terra-Signature')
    if not signature_header:
        return False, "Missing signature header"

    # 2. Parse signature header
    parts = dict(item.split('=') for item in signature_header.split(','))

    try:
        timestamp = int(parts.get('t', 0))
        received_signature = parts.get('v1', '')
    except (ValueError, AttributeError):
        return False, "Invalid signature format"

    # 3. Verify timestamp
    current_time = int(time.time() * 1000)
    age = current_time - timestamp

    if abs(age) > TIMESTAMP_TOLERANCE:
        return False, "Timestamp too old or in future"

    # 4. Get raw body
    body = request.get_data(as_text=True)

    # 5. Construct signed string
    signed_string = f"{timestamp}.{body}"

    # 6. Calculate expected signature
    expected_signature = hmac.new(
        SIGNING_SECRET.encode('utf-8'),
        signed_string.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # 7. Compare signatures (constant-time)
    if not hmac.compare_digest(expected_signature, received_signature):
        return False, "Signature mismatch"

    return True, None

@app.route('/webhooks/terra', methods=['POST'])
def webhook_handler():
    valid, error = verify_webhook(request)

    if not valid:
        abort(401, description=error)

    # Process webhook
    data = request.get_json()
    print(f"Received webhook: {data}")

    return '', 200

if __name__ == '__main__':
    app.run(port=3000)
    

Headers

Every webhook Terra sends include these headers

Header
Description
Example

X-Terra-Signature

HMAC signature with timestamp

t=1700000000000,v1=a1b2c3...

X-Terra-Trace-Id

Unique request ID for debugging

251285377982321505

Content-Type

Always application/json

application/json

Signature Format

The X-Terra-Signature header follows this format:

  • t = Unix timestamp in milliseconds when the webhook was sent

  • v1 = HMAC-SHA256 signature in hexadecimal format

Example:

Body

Event Type
Description
Webhook Payload

fulfilment.payment_processing

This is the default event returned once an order is placed. Not actually returned as an individual webhook.

Look at hint above

fulfilment.payment_complete

This event is sent once payment is confirmed

fulfilment.delivery_fulfilled

This event is sent once delivery details such as tracking number is available

results.kit_activated

The event is sent once an end user activates his/her kit. Only applies to suppliers which support 2-step activation process

results.sample_processing_in_lab

This event is sent when test receipt is confirmed by the lab

results.results_ready

This event is sent once result availability is confirmed by lab

results.sample_rejected

This event is sent if the sample is rejected by the lab

results.escalation_raised

This event is sent if there is an escalation in the result set

Last updated

Was this helpful?