Appearance
Using Webhooks
This document explains how to receive real-time event notifications from Spark using webhooks. Webhooks allow Spark to send HTTP POST requests to your server when configured events occur, such as a price release.
Overview
A webhook integration consists of:
- A webhook endpoint on your server (a publicly accessible URL).
- A registered webhook endpoint in the Spark Dashboard that Spark will call.
- Logic in your endpoint to receive and process events.
When Spark triggers an event, it sends the event payload as JSON using an HTTP POST request. The event payload matches the structure of the “Latest Price Release” API response.
Triggers
You can create webhooks to receive the following four event types:
new-lng-freight-physical-price-release— when a new LNG Freight physical price release is publishednew-lng-freight-financial-price-release— when a new LNG Freight financial price release is publishednew-lng-cargo-physical-price-release— when a new LNG Cargo physical price release is publishednew-lng-cargo-financial-price-release— when a new LNG Cargo financial price release is published
Register a New Webhook
Go to the Spark application to create a webhook. Save the Signing Secret in a safe place. It is only visible on the application for a few minutes.
You must provide:
- Your server URL (note: only HTTPS endpoints are allowed).
- The event type that will trigger the webhook. As you cannot select more than one type, you may need to create multiple webhooks.
You can optionally provide a description for your own reference.
Payload
A webhook payload follows this structure:
json
{
"webhookUuid": "<unique webhook id>",
"type": "<trigger type>",
"releaseDate": "<price release date, ISO format>",
"attempt": 1,
"data": [{ "...": "..." }, { "...": "..." }],
"dataFormat": "/value.0/contracts/<ticker>/price-releases/"
}attemptstarts at1and increments on retries.datacontains the actual price release payload.dataFormatindicates which API endpoint specification the data follows.
Your logic should inspect type and process the content accordingly.
Verifying Request Authenticity
Spark HTTP Headers
Every webhook request includes two headers:
Spark-Webhook-Delivery-IdSpark-Webhook-Signature
Spark-Webhook-Delivery-Id is the unique ID of the current delivery. Two webhook executions containing the same data will always have different delivery IDs.
The Spark-Webhook-Signature header contains:
- A timestamp (
ts=...) - A signature hash (
value=...)
You should always both verify this signature and this timestamp to ensure the request originated from Spark.
Verification Steps
- Parse the header to extract the timestamp and the
valuesignature. - Construct the signed payload string:
signed_payload = {timestamp}.{raw_request_body} - Compute the expected signature using HMAC SHA-256 with your endpoint’s secret as the key.
- Compare the computed signature with the one from the header using a constant-time comparison.
- To protect against replay attacks, verify that the timestamp is recent (e.g. less than 5 minutes old).
Important: Only use the value signature. Any other schemes, if present, must be ignored.
Testing Your Webhook
Use the Spark application to test your webhook integration:
- Select the webhook to open its details page.
- Click Send test webhook.
- Check the webhook logs.
- Check your server logs to inspect the received payload.
This helps validate that your logic correctly receives and processes events.
Best Practices
Monitor Delivery and Failures
Implement logging around webhook deliveries, signature verification, and event processing for troubleshooting.
Secure Your Endpoint
- Always verify the signature.
- Use HTTPS for your webhook endpoints.
Example Implementation (Python)
Below is an example using Flask that:
- Verifies the Spark signature
- Parses the JSON payload
- Handles basic events
python
import hashlib
import hmac
import json
import os
from time import time
from flask import Flask, request
app = Flask(__name__)
SIGNING_SECRET = os.environ["SPARK_WEBHOOK_SIGNING_SECRET"]
MAX_TIMESTAMP_AGE = 300 # 5 minutes in seconds
def verify_signature(payload: bytes, signature_header: str) -> bool:
# Parse header string: "ts=...,value=..."
items = dict(item.split("=", 1) for item in signature_header.split(","))
timestamp = items.get("ts")
signature = items.get("value")
if not timestamp:
print(f"No timestamp found. {signature_header=}")
return False
if not signature:
print(f"No signature found. {signature_header=}")
return False
signed_payload = f"{timestamp}.{payload.decode()}"
computed_signature = hmac.new(
key=SIGNING_SECRET.encode(),
msg=signed_payload.encode(),
digestmod=hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(computed_signature, signature):
print("Signatures differ")
return False
# Verify timestamp is recent to prevent replay attacks
try:
timestamp_int = int(timestamp)
except ValueError:
print(f"Timestamp should be an int: {timestamp}")
return False
age = int(abs(time() - timestamp_int))
if age > MAX_TIMESTAMP_AGE:
print(f"The payload is too old: {age}s")
return False
return True
@app.route("/webhooks", methods=["POST"])
def webhook():
raw = request.data
signature_header = request.headers.get("Spark-Webhook-Signature", "")
if not verify_signature(raw, signature_header):
return "Invalid signature", 400
event = json.loads(raw)
event_type = event.get("type")
delivery_uuid = request.headers.get("Spark-Webhook-Delivery-Id", "")
print(f"New '{event_type}' webhook, {delivery_uuid=}")
return "", 200Save this code sample as webhook_server.py. Make sure to not call your application flask.py because this would conflict with Flask itself.
To run the application, use the flask command or python -m flask: $ flask --app webhook_server run.