Skip to main content

Webhooks

Webhooks push real-time events to your endpoint — a new transaction, a completed extraction, a viewed disclosure — so you can react instead of polling. Send them to Zapier, Slack, your CRM, your own service, or any HTTPS URL. Want deal events in GoHighLevel or another CRM? See Send deal data to a CRM.

You can manage webhook subscriptions two ways:

  • In DocJacket — Settings → Connections (create, edit, view delivery stats).
  • Via the API — with your mcp_at_ key, for fully programmatic setup.

Manage subscriptions with the API​

# List the event types you can subscribe to
curl https://api.docjacket.com/api/v1/webhook-events \
-H "Authorization: Bearer mcp_at_YOUR_KEY"

# Create a subscription (requires the `actions` scope)
curl -X POST https://api.docjacket.com/api/v1/webhooks \
-H "Authorization: Bearer mcp_at_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ops Slack relay",
"url": "https://example.com/hooks/docjacket",
"events": ["extraction.completed", "task.completed"]
}'

The create response includes the signing secret in full, exactly once — store it; you'll need it to verify signatures. Later reads only show a masked prefix. Other operations:

MethodPathScopePurpose
GET/api/v1/webhooksreadList your subscriptions
GET/api/v1/webhooks/{id}readGet one subscription
POST/api/v1/webhooksactionsCreate a subscription
PUT/api/v1/webhooks/{id}actionsUpdate url / events / active
DELETE/api/v1/webhooks/{id}actionsDelete a subscription
POST/api/v1/webhooks/{id}/testactionsFire a test delivery now
POST/api/v1/webhooks/{id}/regenerate-secretactionsRotate the signing secret
GET/api/v1/webhooks/{id}/deliveriesreadRecent delivery attempts
GET/api/v1/webhook-eventsreadThe event catalog

Subscribe to "*" to receive every event type.

Event catalog​

EventFires when
contact.createdWhen a new contact is created
disclosure.recipient_viewedWhen a disclosure package recipient opens the disclosure
document.deletedWhen a document is deleted
document.uploadedWhen a document is uploaded to a transaction
extraction.completedWhen a document extraction job finishes (includes jobId + status)
form.submission_receivedWhen a public form submission is received
key_date.completedWhen a key date is marked complete
key_date.updatedWhen a key date is added or changed on a transaction
task.completedWhen a task is marked complete
task.createdWhen a new task is created
transaction.archivedWhen a transaction is archived
transaction.closedWhen a transaction moves into a closed status, by any route (includes agents, office, price, TC fee and closing date)
transaction.createdWhen a new transaction is created
transaction.deletedWhen a transaction is deleted
transaction.status_changedWhen a transaction status changes (includes the deal snapshot)
webhook.testTest webhook (triggered manually)

The same list is available at runtime from GET /api/v1/webhook-events, and in the OpenAPI specification under webhooks — where each event also carries its payload schema and an example.

extraction.completed turns a document upload into a round-trip: subscribe, upload via the API, and receive the signed callback with the jobId and confidence when extraction finishes — no polling.

Payload​

Every delivery is a POST with a JSON body in this envelope:

{
"eventType": "extraction.completed",
"eventId": "b1c2…",
"timestamp": "2026-07-04T13:00:00Z",
"organizationId": "org-…",
"userId": null,
"data": {
"jobId": "…",
"transactionId": "…",
"documentId": "…",
"status": "completed",
"confidence": 0.98,
"requiresReview": false
}
}

The shape of data depends on the event. Most events are thin: they carry IDs and a few key fields, and you fetch the full resource from the REST API when you need more. The exception is the deal events (transaction.created, transaction.status_changed, transaction.closed), which also carry a full deal snapshot (address, status, price, closing date, TC fee, commissions, office, assignee, agents and custom fields), so you rarely need a follow-up call. Fields may be added over time; ignore ones you don't recognize.

Deal events​

Here is a transaction.status_changed delivery:

{
"eventType": "transaction.status_changed",
"eventId": "3f2a91c7-5d64-4e18-9b03-8c7d1e4a6f52",
"timestamp": "2026-08-04T15:42:07.1183940Z",
"organizationId": "org-9fc8a1d2e3b4",
"userId": "1c9e6b40-72af-4d35-8e21-5b0a3f7c9d16",
"userName": "Jordan Rivera",
"data": {
"id": "8a4c2e10-9f5b-4713-b6d8-0e2a7c5f3149",
"oldStatus": "Under Contract",
"newStatus": "Closed",
"deal": {
"id": "8a4c2e10-9f5b-4713-b6d8-0e2a7c5f3149",
"address": "144 Peachtree Walk NE",
"city": "Atlanta",
"state": "GA",
"zip": "30309",
"county": "Fulton",
"side": "Buyer",
"type": "Purchase",
"status": "Closed",
"purchasePrice": 485000,
"closingDate": "2026-09-15",
"tcFee": 395,
"commissionAmount": 14550,
"commissionRate": 3,
"totalCommissionAmount": 29100,
"buyerAgentCommissionAmount": 14550,
"sellerAgentCommissionAmount": 14550,
"buyerAgentCommissionRate": 3,
"sellerAgentCommissionRate": 3,
"office": {
"id": "c3a8e1f4-7b20-4d59-a6e3-9f0b2d4c8e17",
"name": "Peachtree Realty Group"
},
"assignedTo": {
"id": "1c9e6b40-72af-4d35-8e21-5b0a3f7c9d16",
"name": "Jordan Rivera",
"email": "jordan@example.com"
},
"agents": [
{ "role": "listing", "name": "Sam Patel", "email": "sam@example.com", "brokerage": "Northside Homes", "phone": "404-555-0142" },
{ "role": "buyer", "name": "Alex Chen", "email": "alex@example.com", "brokerage": "Peachtree Realty Group", "phone": "404-555-0187" }
],
"customFields": {
"lockbox_code": "4417",
"hoa_transfer_fee": null
}
}
}
}

Commission rates are in percentage points (3 means 3%). commissionAmount and commissionRate are for your side of the deal; on a dual-agency deal they add both sides together. customFields holds only your active custom fields, keyed by merge code; each value is the stored text, or null when unset.

A deal that moves into a closed status sends transaction.status_changed and transaction.closed; both carry the same deal snapshot. For a plain-language walkthrough of sending these to a CRM, with a table of every deal field, see Send deal data to GoHighLevel or another CRM.

Each request also carries these headers:

HeaderValue
X-Webhook-Signaturesha256=<hmac-hex> — see below
X-Webhook-Event-Typee.g. extraction.completed
X-Webhook-Event-IdUnique per delivery attempt
X-Webhook-TimestampISO 8601 send time

Verify the signature​

DocJacket signs every payload with HMAC-SHA256 using your subscription's secret. Always verify before trusting a delivery — compute the HMAC of the raw request body and compare it to the X-Webhook-Signature header.

// Node.js (Express) — use the RAW body, not the parsed JSON
import crypto from 'crypto';

function verify(rawBody, signatureHeader, secret) {
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(signatureHeader);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post('/hooks/docjacket',
express.raw({ type: 'application/json' }),
(req, res) => {
if (!verify(req.body, req.get('X-Webhook-Signature'), process.env.DJ_WEBHOOK_SECRET)) {
return res.status(401).end();
}
const event = JSON.parse(req.body);
// …handle event.eventType / event.data…
res.status(200).end();
});
# Python (Flask)
import hmac, hashlib

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

Return a 2xx quickly (do heavy work asynchronously). Any non-2xx — or a timeout — counts as a failed delivery.

Retries, circuit breaker & delivery history​

  • Retries — failed deliveries retry with backoff.
  • Circuit breaker — if an endpoint fails repeatedly, DocJacket automatically pauses it to avoid flooding a down service. Re-enable it (and reset the breaker) once you've fixed the endpoint.
  • Delivery history — GET /api/v1/webhooks/{id}/deliveries (or the Connections page) shows recent attempts with status codes, latency, errors, and the next retry time, so you can self-debug.

Test it​

Fire a test delivery to confirm your endpoint and signature check work before going live:

curl -X POST https://api.docjacket.com/api/v1/webhooks/SUBSCRIPTION_ID/test \
-H "Authorization: Bearer mcp_at_YOUR_KEY"