Agent skill

Drata Webhooks Skill

Receive and authenticate Drata workflow webhooks sent by the "Send webhook" step. Use when building a Drata webhook receiver, securing it with a shared secret custom header (Drata documents no signing secret), handling event_type values like CONTROL_EVIDENCE_LINKED, CONTROL_READINESS_CHANGED_TO_NOT_READY or CONTROL_TEST_CHANGED_TO_FAIL, or debugging Drata compliance automation webhook deliveries.

Install this skill

npx skills add hookdeck/webhook-skills --skill drata-webhooks


Drata doesn't have webhook subscriptions. A webhook is a step inside a Drata workflow: Settings → Workflows → Create Workflow → add a "Send webhook" step. When the workflow's trigger fires (for example, evidence linked to a control), Drata sends a POST to the URL you entered, with the headers and properties you configured.

This differs from most providers in three ways:

  1. There is no documented signature. Drata's help center describes authentication "as custom headers" and nothing else. It mentions that Drata uses Svix to send webhooks, but it documents no svix-* headers and no whsec_ signing secret, so don't build verification around them.
  2. You choose what's in the body. You pick the control and trigger properties to include. Every body has the base properties event_type, timestamp, organization_id and workspace.
  3. You choose the headers. Auth headers and extra context headers are set per step.

When to Use This Skill

  • How do I receive Drata webhooks?
  • How do I verify Drata webhook signatures? (You can't. Send a shared secret in a custom header instead.)
  • How do I set up the "Send webhook" step in a Drata workflow?
  • What does a Drata webhook payload look like? (event_type, timestamp, control_* fields.)
  • How do I handle CONTROL_EVIDENCE_LINKED or CONTROL_TEST_CHANGED_TO_FAIL?
  • Why does my Drata webhook arrive more than once? (Failed steps are retried up to three times.)

Verification (core): a shared secret custom header

In the Send webhook step, add a custom header with a long random value:

X-Webhook-Secret: 3f9c0e...a long random hex value...

The header name is your choice. Check it in constant time before you parse the body:

const crypto = require('crypto');

// NOT a Drata signature. This is a secret YOU put in the step's custom headers.
function verifyDrataSecret(headerValue) {
  const expected = process.env.DRATA_WEBHOOK_SECRET;
  if (!expected) throw new Error('DRATA_WEBHOOK_SECRET is not set'); // fail closed
  if (typeof headerValue !== 'string') return false;
  const a = Buffer.from(headerValue);
  const b = Buffer.from(expected);
  // Length check first: timingSafeEqual throws on a length mismatch.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: verifyDrataSecret(req.get('X-Webhook-Secret'))

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Return 401 for a missing or wrong secret and 500 when the secret isn't configured, so the endpoint never accepts everything by accident. Read the body raw (express.raw({ type: '*/*' })) and parse JSON only after the check. See references/verification.md.

Payload

Drata documents the body as three groups of properties:

  • Base (always): event_type, timestamp, organization_id, workspace
  • Control properties (the ones you select): control_name, control_code, control_id, control_owners, control_readiness, control_description, control_approval_status, control_approvers, control_mapped_requirements, control_mapped_tests, control_evidence, control_policies, control_notes, control_custom_fields, control_workspace_name, control_workspace_id
  • Trigger-specific: e.g. evidence_name and linked_by for CONTROL_EVIDENCE_LINKED, test_name for CONTROL_TEST_CHANGED_TO_FAIL

Dates are ISO strings, booleans are true/false, and arrays (owners, etc.) are JSON. Drata's help center doesn't show a full example body, so treat any field you didn't select as absent.

Events (event_type values for control workflows)

event_typeFires when
CONTROL_DETAILS_UPDATEDControl details are edited (updated_by)
CONTROL_READINESS_CHANGED_TO_NOT_READYA control becomes Not ready (date_changed)
CONTROL_READINESS_CHANGED_TO_READYA control becomes Ready (date_changed)
CONTROL_EVIDENCE_LINKED / CONTROL_EVIDENCE_UNLINKEDEvidence is linked or unlinked
CONTROL_POLICY_LINKED / CONTROL_POLICY_UNLINKEDA policy is linked or unlinked
CONTROL_TEST_MAPPED / CONTROL_TEST_UNMAPPEDA test is mapped or unmapped
CONTROL_REQUIREMENT_MAPPED / CONTROL_REQUIREMENT_UNMAPPEDA requirement is mapped or unmapped
CONTROL_TEST_CHANGED_TO_PASS / CONTROL_TEST_CHANGED_TO_FAILA mapped test starts passing or failing
CONTROL_OWNER_UPDATEDThe control owner changes (new_control_owner, old_control_owner, updated_by)
CONTROL_MARKED_IN_SCOPE / CONTROL_MARKED_OUT_OF_SCOPEScope changes (changed_by, date_changed)

Workflows for risks, evidence and personnel also support webhooks, but the pages this skill is based on only list event_type values for control workflows. Log unknown values and return 200; a body with no event_type gets 400.

Delivery, Retries and Idempotency

  • Steps in a workflow run in parallel. If one fails, "Drata will automatically retry the failed step up to three times". Return a 2xx quickly.
  • There's no delivery ID header in Drata's documentation, so dedupe on body fields (this skill uses event_type + control_id + timestamp).
  • Drata doesn't document a timestamp tolerance. timestamp is the event time, not a signed value, so don't use it to reject requests.

Environment Variables

# REQUIRED. The value you put in the step's custom header. Generate with: openssl rand -hex 32
DRATA_WEBHOOK_SECRET=
# OPTIONAL. The custom header name (case-insensitive). Default: X-Webhook-Secret
DRATA_WEBHOOK_HEADER=X-Webhook-Secret

Local Development

npx hookdeck-cli listen 3000 drata --path /webhooks/drata

No account required. The CLI creates a guest account on first run and gives you a public HTTPS URL to paste into the Send webhook step, plus a web UI for inspecting requests. See references/setup.md.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →