Guide to Streak Webhooks: Features and Best Practices
Streak webhooks notify your systems when something changes in Streak, the CRM that runs inside Gmail. When a box moves stage, a task falls due or a contact is updated, Streak can send the changed object to your service, so you can sync deals to a data warehouse, start an onboarding workflow when a deal is won, or alert a team in another tool.
This guide covers how to create Streak webhooks through the API, how to authenticate them with a token, which events exist, and the practices that make a Streak receiver dependable.
What are Streak webhooks?
A Streak webhook subscribes one target URL to one event. Streak "will POST an object to a target URL when certain events happen", and the "updated object(s) will be in an array". The event decides the kind of object: a box, stage, comment, meeting note or call log, task, contact or organisation.
Webhooks are either pipeline-based or team-based. Box, stage, comment, meeting and task events belong to a pipeline, and contact and organisation events belong to a team. You choose by passing a pipelineKey or a teamKey when you create the webhook, and you can't switch a webhook between the two later.
Streak doesn't sign requests. When you create a webhook you can give it an optional token, which Streak includes "as a header (X-Streak-Webhook-Token) in every request sent to the target url". That token is the only way to tell a real Streak request from a forged one, and a webhook created without it sends nothing to check.
Streak webhook features
| Feature | Details |
|---|---|
| Configuration | Streak API only: POST https://api.streak.com/api/v2/webhooks |
| Scope | One event per webhook, on a pipeline (pipelineKey) or a team (teamKey) |
| Method | POST |
| Body | The updated object or objects, in an array |
| Authentication | Optional token, sent as X-Streak-Webhook-Token |
| Signature and timestamp | None |
410 Gone response | The webhook deletes itself |
Other non-200 responses | Retried with a backoff delay |
| Retry count, timeout, source IPs | Not documented |
Events
| Event | Category | Object | Sent when |
|---|---|---|---|
BOX_CREATE | Pipeline | Box | A new box is created |
BOX_DELETE | Pipeline | Box | A box is deleted |
STAGE_CREATE | Pipeline | Stage | A new stage is created |
BOX_NEW_EMAIL_ADDRESS | Pipeline | Box | A new email address is detected on a box |
BOX_EDIT | Pipeline | Box | A custom field, notes, assigned to or name is edited on a box |
BOX_CHANGE_STAGE | Pipeline | Box | A box changes stage |
BOX_CHANGE_PIPELINE | Pipeline | Box | A box changes pipeline |
COMMENT_CREATE | Pipeline | Comment | A comment is created |
MEETING_CREATE | Pipeline | Meeting note or call log | A meeting note or call log is created in a box |
MEETING_UPDATE | Pipeline | Meeting note or call log | A meeting note or call log changes |
TASK_CREATE | Pipeline | Task | A task is created |
TASK_COMPLETE | Pipeline | Task | A task is completed |
TASK_DUE | Pipeline | Task | A task is due |
TASK_UPDATED | Pipeline | Task | Anything about a task changes, such as text, due date, status or assignee |
CONTACT_CREATE | Team | Contact | A contact is created |
CONTACT_UPDATE | Team | Contact | A contact is updated |
CONTACT_DELETE | Team | Contact | A contact is deleted |
ORG_CREATE | Team | Organisation | An organisation is created |
ORG_UPDATE | Team | Organisation | An organisation is updated |
ORG_DELETE | Team | Organisation | An organisation is deleted |
Streak's docs don't show a delivered payload or say whether the body names the event. Because each webhook has exactly one event, the safe approach is to put the event in the target URL, for example https://example.com/webhooks/streak?event=BOX_, and route on that. Always treat the body as an array, even when one object changed.
Inspect Streak webhooks as they arrive. Create a webhook with a Hookdeck Inspect URL as the target to see the exact headers and body for each event, with no account or setup required.
Setting up Streak webhooks
Streak webhooks are created through the API. Generate a long random token first, for example with openssl rand -hex 32.
- In Gmail, open the Streak sidebar, go to Integrations, and under Streak API click Create New Key.
- Find the key of the pipeline (for pipeline events) or team (for team events) you want to watch.
- Create the webhook. Streak's API uses HTTP Basic auth with your API key as the username and no password:
curl -X POST "https://api.streak.com/api/v2/webhooks?pipelineKey=YOUR_PIPELINE_KEY" \
-u "YOUR_API_KEY:" \
-H "Content-Type: application/json" \
-d '{
"event": "BOX_CHANGE_STAGE",
"targetUrl": "https://example.com/webhooks/streak?event=BOX_CHANGE_STAGE",
"token": "YOUR_WEBHOOK_TOKEN"
}'
- Repeat for each event you need. Use
teamKeyinstead ofpipelineKeyfor contact and organisation events.
The token is optional in the API, so check every existing webhook. To add or change a token on one, update it with POST /api/v2/webhooks/{webhookKey}, which takes the same token field.
To route through Hookdeck, create a source with the Streak type and use its URL, with your ?event= query string, as the targetUrl. Enter the same token in the source's Webhook Token field. The source compares it with the X-Streak-Webhook-Token header and accepts POST.
Securing Streak webhooks
Compare the whole X-Streak-Webhook-Token header with the token you set, in constant time. Reject requests with no header: a webhook without a token can't be told apart from a forgery.
const crypto = require('crypto');
const express = require('express');
const TOKEN = process.env.STREAK_WEBHOOK_TOKEN;
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on unequal lengths, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/streak', express.raw({ type: '*/*' }), (req, res) => {
const received = req.get('x-streak-webhook-token');
if (!TOKEN || typeof received !== 'string' || !safeEqual(received, TOKEN)) {
return res.sendStatus(401);
}
let objects;
try {
objects = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
const event = req.query.event; // set when you created the webhook
const items = Array.isArray(objects) ? objects : [objects];
// Queue { event, items }, then respond. Never return 410 unless you
// mean to delete the webhook.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
TOKEN = os.environ.get("STREAK_WEBHOOK_TOKEN", "")
def verify_streak_request(headers) -> bool:
if not TOKEN:
return False # fail closed when unconfigured
received = headers.get("x-streak-webhook-token")
if not received:
return False
# Compare bytes: compare_digest raises TypeError on a non-ASCII str.
return hmac.compare_digest(received.encode("utf-8"), TOKEN.encode("utf-8"))
A static token authenticates the sender, not the body. It can't detect a modified payload, and with no signed timestamp a captured request can be replayed and still pass. Serve the endpoint over HTTPS only, use a long random token, and change it on the webhook and in your receiver if it might have leaked.
Make Streak webhooks production-ready. Hookdeck Event Gateway checks the token on every Streak request, stores each event durably, and lets you replay any delivery after a fix.
Streak webhook limitations and pain points
The token is optional
The Problem: Requests arrive with nothing to authenticate them.
Why It Happens: token is an optional field when you create a webhook, and a webhook created without it sends no X-Streak-Webhook-Token header.
Workarounds:
- Always pass a token when creating a webhook, and add one to existing webhooks with the update endpoint.
- Fail closed when the header is missing.
How Hookdeck Can Help: Hookdeck's Streak source requires the token. Hookdeck answers Streak with a 200 either way, but a request with a missing or wrong token is recorded as failed verification and never reaches your destination.
A 410 deletes the webhook
The Problem: A webhook disappears, and events stop without an error.
Why It Happens: Streak treats 410 Gone as a request to delete the webhook. A framework default, a proxy or a retired route that answers 410 removes it.
Workarounds:
- Never return
410from a Streak endpoint unless you mean it. - List your webhooks through the API on a schedule and recreate any that are missing.
How Hookdeck Can Help: Hookdeck answers Streak itself, so a 410 from your service never reaches Streak and can't delete the webhook. The failed delivery stays in Hookdeck for you to retry.
Undocumented retry limits
The Problem: You don't know how long Streak keeps retrying after an outage on your side.
Why It Happens: Streak says it retries "with a backoff delay" but not how many times or how long it waits for a response.
Workarounds:
- Respond with
200quickly and do the work afterwards. - After an outage, re-read the affected pipelines or contacts from the API.
How Hookdeck Can Help: Hookdeck accepts each event as soon as it arrives and delivers it to your service with retries you configure, so recovery doesn't depend on Streak's schedule.
One webhook per event
The Problem: Watching a pipeline fully means managing many webhooks.
Why It Happens: Each webhook has a single event.
Workarounds:
- Create webhooks from a script that holds the list of events, URLs and tokens, so they stay consistent.
- Point them all at one endpoint, with the event in the query string.
How Hookdeck Can Help: All the webhooks can share one Hookdeck source, and Hookdeck filters can route each event to a different destination based on its path or query.
Best practices
Put the event in the URL
Add ?event=... to each targetUrl so your receiver knows which event fired without relying on the body.
Handle arrays
The body is an array of updated objects. Process each one, and don't assume there is only one.
Make processing idempotent
Retries can repeat an event. Key your work on the object's key and the state you are applying, and skip work you have done. See our guide to webhook idempotency.
Acknowledge fast and process asynchronously
Check the token, store the event, return 200, and do the work afterwards. See why to process webhooks asynchronously.
Conclusion
Streak webhooks are created through the API, one event per webhook, and send the updated objects in an array, authenticated only by an optional token in the X-Streak-Webhook-Token header. Set a token on every webhook, compare it whole in constant time, put the event in the target URL, and never answer 410 unless you want the webhook gone.
Hookdeck Event Gateway sits in front of your receiver, checks the token, and keeps every event until your service has processed it.
Get started with Hookdeck for free and handle Streak webhooks reliably in minutes.