Skip to main content

Receive Real-Time Contract Events via Webhooks

Goal

Subscribe to SpotDraft contract lifecycle events so your integration can react to review, signature, and execution changes without polling every contract.

When to use this

Use this when your integration needs low-latency lifecycle updates for review, signature, execution, or repository events.

Endpoints used

  • GET /api/v2.1/public/webhooks/hmac_key/
  • POST /api/v2.1/public/webhooks/
  • DELETE /api/v2.1/public/webhooks/{webhook_id}

Prerequisites

  • a public HTTPS endpoint that accepts POST requests
  • access to the raw request body in your webhook receiver
  • durable storage or a queue for event processing
  • a credential that can access the target workspace
  • Developer Settings permission if you will create or delete webhook subscriptions through the API

Overview

Persist the webhook id returned from the create call. You need that exact id for later rotation or deletion.

The HMAC-key fetch endpoint and the create/delete endpoints have different permission behavior:

  • GET /webhooks/hmac_key/ only requires workspace access
  • POST /webhooks/ and DELETE /webhooks/{webhook_id} also require Developer Settings permission

1. Fetch the HMAC key used to verify webhook payloads

import requests

base_url = "https://api.in.spotdraft.com" # Replace with your workspace region.
headers = {
"client-id": SPOTDRAFT_CLIENT_ID,
"client-secret": SPOTDRAFT_CLIENT_SECRET,
"Content-Type": "application/json",
}

hmac_response = requests.get(
f"{base_url}/api/v2.1/public/webhooks/hmac_key/",
headers=headers,
timeout=30,
)
hmac_response.raise_for_status()
hmac_key = hmac_response.json()["hmac_key"]

Persist the returned key securely. Use it only for webhook verification.

Example response:

{
"hmac_key": "BASE64_ENCODED_SECRET"
}

2. Register the webhook

webhook_payload = {
"title": "Contract Created webhook",
"url": "https://yourdomain.com/webhooks/event-handler",
"only_for_activities": [
"CONTRACT_CREATED",
"CONTRACT_EXECUTED",
"CONTRACT_SENT_TO_COUNTERPARTY",
],
}

webhook_response = requests.post(
f"{base_url}/api/v2.1/public/webhooks/",
headers=headers,
json=webhook_payload,
timeout=30,
)
webhook_response.raise_for_status()
webhook = webhook_response.json()

3. Verify the signature in your receiver

Verify X-SD-WEBHOOK-CONTENT-HASH against the raw request body before any business processing.

import base64
import hashlib
import hmac

def verify_spotdraft_webhook(raw_body: bytes, received_hash: str, hmac_key: str) -> bool:
computed_hash = hmac.new(
base64.b64decode(hmac_key),
raw_body,
digestmod=hashlib.sha512,
).hexdigest()
return hmac.compare_digest(computed_hash, received_hash)

4. Persist first, process later

Recommended production behavior:

  • verify the signature
  • persist the raw payload and headers
  • deduplicate by delivery id or payload fingerprint
  • enqueue downstream work
  • return 2xx quickly

Downstream work typically includes:

  • syncing contract status back into your own system
  • fetching contract metadata or content after execution
  • generating a download link for the signed document

5. Remove or rotate a webhook

If you need to replace an endpoint, delete the old subscription explicitly.

webhook_id = os.environ["SPOTDRAFT_WEBHOOK_ID"]
delete_response = requests.delete(
f"{base_url}/api/v2.1/public/webhooks/{webhook_id}",
headers=headers,
timeout=30,
)
delete_response.raise_for_status()

Production checklist

  • webhook id
  • subscribed activity types
  • verified payload body
  • contract id and external metadata from the event payload
  • your own processing state for each delivery

Production notes

  • Persist the webhook id returned from the create call and use that exact id for later rotation or deletion.
  • Verify signatures against the raw request body bytes before downstream processing.
  • Return 2xx only after verification and persistence, not after expensive business logic.

Common failure points

  • deleting the wrong webhook Persist the id returned from the create call and use that exact value for delete operations.

  • signature verification on parsed JSON Verify against the raw request body bytes, not a reserialized JSON object.