Agent skill

Streak Webhooks Skill

Receive and authenticate Streak webhooks. Use when setting up a Streak CRM webhook endpoint, verifying the X-Streak-Webhook-Token header, debugging 401s, or handling events like BOX_CREATE, BOX_EDIT, BOX_CHANGE_STAGE, COMMENT_CREATE and TASK_CREATE.

Install this skill

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


Streak (the CRM inside Gmail) POSTs the updated object(s), as a JSON array, to your targetUrl when a pipeline or team event happens. Webhooks are not signed. If you set a token when you created the webhook, Streak sends it as plain text in the X-Streak-Webhook-Token header and your endpoint authenticates the request by comparing that whole value with the token you stored.

Set a token when you create the webhook through the Streak API. Webhooks without one send nothing to verify. The token is optional in the API, so a webhook created without it can't be authenticated at all.

A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks: the update endpoint POST /webhooks/{webhookKey} lists token in its schema (its prose says only event and targetUrl are applied, so confirm with a delivery); otherwise delete and recreate the webhook with a new token.

When to Use This Skill

  • How do I receive Streak webhooks?
  • How do I verify the X-Streak-Webhook-Token header?
  • Does Streak sign webhook payloads? (No: use the shared token)
  • How do I handle BOX_CREATE, BOX_CHANGE_STAGE or COMMENT_CREATE events?
  • Why does my Streak webhook have nothing to verify?
  • Why did my Streak webhook disappear? (Your endpoint returned 410 Gone)

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyStreakRequest(headers, token = process.env.STREAK_WEBHOOK_TOKEN) {
  if (!token) return false;                          // never accept when unconfigured
  const received = headers['x-streak-webhook-token']; // whole value, plain text
  return Boolean(received) && safeEqual(received, token);
}

Python: hmac.compare_digest(received.encode(), token.encode()). Always encode both sides to bytes; passing str raises on non-ASCII.

  • Header: X-Streak-Webhook-Token: <token> (name is case-insensitive). The value is the token you passed to POST /api/v2/webhooks, compared whole.
  • Missing header or mismatch: return 401. Body that isn't a JSON object/array: 400. Handled: 200.
  • Read the body raw (express.raw({ type: '*/*' })) and authenticate before parsing it.
  • Return 200 on success. 410 Gone makes Streak delete the webhook; any other non-200 response is retried with a backoff delay.

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

Event Types

The event decides which object is sent (Box, Stage, Comment, Meeting note/Call log, Task, Contact, Organization). The body may not name the event, so create one webhook per event and give each its own URL or query parameter (for example /webhooks/streak?event=BOX_CREATE).

EventCategoryObjectSent When
BOX_CREATEPipelineBoxA box is created
BOX_DELETEPipelineBoxA box is deleted
BOX_EDITPipelineBoxA custom field, notes, assignee or name is edited
BOX_CHANGE_STAGEPipelineBoxA box changes stage
BOX_CHANGE_PIPELINEPipelineBoxA box changes pipeline
BOX_NEW_EMAIL_ADDRESSPipelineBoxA new email address is detected on a box
STAGE_CREATEPipelineStageA stage is created
COMMENT_CREATEPipelineCommentA comment is created
MEETING_CREATE / MEETING_UPDATEPipelineMeeting note/Call logA meeting note or call log is created / changed
TASK_CREATEPipelineTaskA task is created
TASK_COMPLETEPipelineTaskA task is completed
TASK_DUEPipelineTaskA task is due
TASK_UPDATEDPipelineTaskAnything about a task changes
CONTACT_CREATE / CONTACT_UPDATE / CONTACT_DELETETeamContactA contact is created / updated / deleted
ORG_CREATE / ORG_UPDATE / ORG_DELETETeamOrganizationAn organization is created / updated / deleted

Pipeline events need a pipelineKey when the webhook is created; team events need a teamKey.

Environment Variables

STREAK_WEBHOOK_TOKEN=your-webhook-token   # the `token` you passed when creating the webhook

Local Development

Streak can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

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

Use the printed URL (plus ?event=...) as targetUrl when creating the webhook.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →