Menu
View Categories

Webhooks

Webhooks let Slapshot notify your server when something happens to a job, so you don’t need to poll the API. When an event occurs we send an HTTP POST request with a JSON body to your endpoint. Every request is signed with HMAC-SHA256 so you can verify it came from Slapshot.

Events

EventSent when
job.createdA job has been created.
inference.completedAn inference in a job has completed.
job.completedEvery inference in a job has finished.

Request format

POSThttps://your-server.example.com/slapshot/webhook
Headers
Content-Typeheader
application/json
X-Signatureheader
sha256=<hex digest>: an HMAC-SHA256 of the raw request body, keyed with your webhook secret.
Body
eventstring
The event type: job.created, inference.completed or job.completed.
data.job_idstring
The ID of the job the event relates to. Use the Retrieve a job endpoint to fetch full details.
Example payload
{
  "event": "job.completed",
  "data": {
    "job_id": "98696f34-39fa-4bf5-b526-6368a32c0eb4"
  }
}

Verifying signatures

Always verify the X-Signature header before trusting a webhook:

  1. Read the raw request body exactly as received. Don’t parse and re-serialize the JSON first, or the signature won’t match.
  2. Compute an HMAC-SHA256 of the raw body using your webhook secret, and hex-encode it.
  3. Strip the sha256= prefix from the header and compare the two values with a constant-time comparison.
Signature check
import hashlib
import hmac

def is_valid_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    received = signature_header.removeprefix("sha256=")
    return hmac.compare_digest(expected, received)

Example handlers

Flask
# Flask
import hashlib
import hmac
import os

from flask import Flask, abort, request

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["SLAPSHOT_WEBHOOK_SECRET"]

@app.post("/slapshot/webhook")
def slapshot_webhook():
    raw_body = request.get_data()  # raw bytes, not request.json
    signature = request.headers.get("X-Signature", "").removeprefix("sha256=")
    expected = hmac.new(WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        abort(403)

    event = request.get_json()
    job_id = event["data"]["job_id"]

    if event["event"] == "job.created":
        print("Job created:", job_id)
    elif event["event"] == "inference.completed":
        print("Inference completed in job:", job_id)
    elif event["event"] == "job.completed":
        print("Job completed:", job_id)

    return {"status": "ok"}
Django
# Django
import hashlib
import hmac
import json
import os

from django.http import HttpResponseForbidden, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

WEBHOOK_SECRET = os.environ["SLAPSHOT_WEBHOOK_SECRET"]

@csrf_exempt
@require_POST
def slapshot_webhook(request):
    raw_body = request.body
    signature = request.headers.get("X-Signature", "").removeprefix("sha256=")
    expected = hmac.new(WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, signature):
        return HttpResponseForbidden("Invalid signature")

    event = json.loads(raw_body)
    job_id = event["data"]["job_id"]

    if event["event"] == "job.completed":
        print("Job completed:", job_id)

    return JsonResponse({"status": "ok"})

Best practices

  • Respond with a 2xx status as quickly as possible, and do any heavy work (downloading outputs, updating your tracker) in a background task.
  • Make your handler idempotent: if you receive the same event twice, processing it again should be safe.
  • Use the job_id in the payload to fetch the latest state from the API rather than relying on the payload alone.
  • Keep your webhook secret out of source control, just like your API key.

To set up a webhook endpoint or get your webhook secret, contact support@slapshot.ai.