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.