Gareth Wilson Gareth Wilson

Guide to TheirStack Webhooks: Features and Best Practices

Published


TheirStack webhooks push hiring and technology signals to your systems as TheirStack finds them: a new job posting that matches your search, a company that starts matching it, or a job that has closed. If you use TheirStack's job and company data for sales prospecting, market research or recruiting, webhooks turn a saved search into a live feed for your CRM, Slack or automation tool.

This guide covers how TheirStack webhooks are set up and signed, the event types and their payloads, how credits and daily limits shape what you receive, and the practices that keep a TheirStack receiver reliable.

What are TheirStack webhooks?

A TheirStack webhook is attached to a saved search. Every time TheirStack finds a new record matching that search, it sends one JSON POST with that one record. Each event costs API credits, the same as an API request.

If you set a signing secret on the webhook, each delivery carries an X-TheirStack-Signature-256 header in the same format GitHub uses: sha256= followed by the hex HMAC-SHA256 of the raw body. Without a secret, the header isn't sent.

TheirStack webhook features

FeatureDetails
ConfigurationThe TheirStack app, or POST /v0/webhooks and PATCH /v0/webhooks/{id}
TriggerA saved job, company or technology search
Records per requestOne
Event typetype in the body
Signature headerX-TheirStack-Signature-256, only when a signing secret is set
SchemeHMAC-SHA256, lowercase hex, prefixed sha256=
Signed contentThe raw request body, nothing else
SecretSet or generated on the webhook, minimum 16 characters
Cost1 credit for job.new and job.closed, 3 for company.new and tech.new
Daily limitOptional max_events_per_day. Records above it are dropped
Success responseAny 2xx
RetriesEvery hour for 48 hours
ConcurrencyYour endpoint must handle at least 2 concurrent requests

TheirStack doesn't publish source IPs or a response timeout.

Common events

EventFires whenpayload
job.newTheirStack discovers a new job posting that matches your searchThe full Job object
company.newA company matches your search for the first time on this webhookThe full Company object
job.closedA matching job is closed or removed by its sourceDescribed as { "id": ..., "closed_at": ... }
tech.newA company adopts a new technologyAccess on request, payload not documented

Every body has the same envelope: a top-level id for the webhook event, the type, and the record under payload:

{
  "id": 123456,
  "type": "job.closed",
  "payload": {
    "id": 123,
    "closed_at": "2026-06-08T14:00:00Z"
  }
}

The values are illustrative: TheirStack publishes field tables for each event type but no example bodies. The payload shape for job.closed comes from TheirStack's endpoint guide. Its event reference page for job.closed lists the full Job schema instead, so handle both.

company.new is "new" relative to the webhook, not to TheirStack's database. A company that has been hiring for years still fires the first time it matches, and never again on that webhook.

job.new payloads are large. Besides title, location and salary fields, they include description (in Markdown), company_object, hiring_team, keyword_slugs and, for keyword searches, matching_phrases. The company string field is deprecated: use company_object or company_domain.

Setting up TheirStack webhooks

  1. Create and save a search in TheirStack: jobs, companies or technologies.
  2. Create a webhook for it, in the app or through the API, with your endpoint URL and the event type.
  3. Set a Signing Secret, at least 16 characters. The app can generate one, or you can pass secret on POST /v0/webhooks or PATCH /v0/webhooks/{id}. Store it server-side, for example as THEIRSTACK_WEBHOOK_SECRET.
  4. Decide on a daily event limit. Without one, a broad search can spend a lot of credits quickly.

If you receive TheirStack webhooks through Hookdeck, create a source with the TheirStack type, paste the signing secret, and use the source URL as the webhook URL. Set a signing secret on the TheirStack webhook: without one, deliveries are unsigned and fail verification.

Securing TheirStack webhooks

Read X-TheirStack-Signature-256, check it starts with sha256=, compute the hex HMAC-SHA256 of the raw body with your secret, and compare in constant time.

const crypto = require('crypto');
const express = require('express');

function verifyTheirStackSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret || !signatureHeader.startsWith('sha256=')) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const received = Buffer.from(signatureHeader.slice('sha256='.length).trim().toLowerCase(), 'utf8');
  const computed = Buffer.from(expected, 'utf8');
  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}

const app = express();

app.post('/webhooks/theirstack', express.raw({ type: '*/*' }), (req, res) => {
  const secret = process.env.THEIRSTACK_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500); // fail closed

  if (!verifyTheirStackSignature(req.body, req.get('x-theirstack-signature-256'), secret)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // Route on event.type; deduplicate on event.type plus event.payload.id.
  res.sendStatus(200);
});

The same check in Python:

import hashlib
import hmac


def verify_theirstack_signature(raw_body: bytes, signature_header, secret: str) -> bool:
    if not signature_header or not secret or not signature_header.startswith("sha256="):
        return False
    received = signature_header[len("sha256="):].strip().lower().encode("utf-8")
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest().encode("utf-8")
    return hmac.compare_digest(received, expected)


# In FastAPI: raw_body = await request.body()
# verify_theirstack_signature(raw_body, request.headers.get("x-theirstack-signature-256"),
#                             os.environ["THEIRSTACK_WEBHOOK_SECRET"])

TheirStack publishes test values for your verifier: the secret It's a Secret to Everybody and the payload Hello, World! give sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17. These are the same values GitHub publishes. The payload isn't JSON, so use it to test the HMAC function on its own, not a handler that parses the body.

TheirStack's own sample returns 403 on a mismatch. Any non-2xx works, but bear in mind that TheirStack retries failed events every hour for 48 hours.

There is no timestamp in the request, so nothing tells you how old it is.

Make TheirStack webhooks production-ready. Hookdeck Event Gateway verifies the X-TheirStack-Signature-256 header, stores every job and company event, and delivers it to your service at a rate it can handle.

TheirStack webhook limitations and pain points

No secret means no signature

The Problem: Your verifier rejects every delivery because the header is missing.

Why It Happens: TheirStack only signs when the webhook has a signing secret. Webhooks created without one send no X-TheirStack-Signature-256 header.

Workarounds:

  • Edit the webhook and add a secret, or PATCH /v0/webhooks/{id} with secret.
  • Treat a missing header as a failure, not as a reason to skip verification.

How Hookdeck Can Help: Hookdeck's TheirStack source verifies the header against your secret. Hookdeck answers TheirStack with a 200 either way, then records a request that fails the check as failed verification, so it never reaches your destination.

Every event costs credits, including duplicates

The Problem: Credits run out faster than expected, and then events stop.

Why It Happens: Each event is billed on its own. Events aren't deduplicated against other webhooks or against records you already pulled through the API, so the same job can be billed twice. When credits run out, TheirStack pauses monitoring until you buy more.

Workarounds:

  • Keep searches narrow and avoid overlapping webhooks.
  • Set max_events_per_day, and use GET /v0/webhooks/volume-estimate?search_id=... to size it.
  • Monitor your credit balance so a pause doesn't go unnoticed.

How Hookdeck Can Help: Hookdeck deduplication can drop repeat deliveries of the same record before they reach your service, so a duplicate costs you a credit but not a second CRM update.

The daily limit drops records

The Problem: Some matching jobs never arrive.

Why It Happens: Records above the daily limit aren't sent and aren't queued for the next day. TheirStack does this on purpose to keep the feed current, and doesn't charge for them.

Workarounds:

  • Raise the limit or narrow the search if you need every match.
  • Check the app's warning, which estimates how many records a day you would lose.

How Hookdeck Can Help: Hookdeck shows every event that did arrive, so you can compare daily volume against what the search matches.

Bursts and concurrency

The Problem: A newly created webhook on a broad search floods your endpoint.

Why It Happens: One record per request, with at least 2 concurrent requests, adds up quickly when a search matches many records at once.

Workarounds:

  • Return 2xx quickly and queue the work.
  • Make sure your handler is safe to run concurrently.

How Hookdeck Can Help: Hookdeck accepts the burst and delivers to your service at a max delivery rate you set, with retries for anything that fails.

Best practices

Verify against the raw body

TheirStack warns that re-serialised JSON can change key order or whitespace, and that payloads can contain Unicode, so handle the body as raw UTF-8 bytes. In Express, use express.raw({ type: '*/*' }). In Next.js, use await request.text(). In FastAPI, use await request.body().

Deduplicate on the record

TheirStack says duplicates are rare but possible, for example when two searches overlap, and suggests deduplicating on id. Key on type and payload.id, so a job's job.new and job.closed don't collide. See our guide to webhook idempotency.

Use job.closed to keep data fresh

If you store jobs from job.new, subscribe to job.closed as well, so closed roles drop out of your pipeline instead of lingering.

Acknowledge fast and process asynchronously

Verify, store, return 200, and enrich or route afterwards. See why to process webhooks asynchronously.

Conclusion

TheirStack sends one record per webhook delivery from a saved search and, when the webhook has a signing secret, signs it with X-TheirStack-Signature-256: sha256=<hex>, an HMAC-SHA256 of the raw body. Always set the secret, route on type, deduplicate on the record ID, and set a daily limit so a broad search doesn't drain your credits.

Hookdeck Event Gateway sits in front of your receiver, verifies every TheirStack signature, and smooths bursts so your service sees events at a pace it can handle.

Get started with Hookdeck for free and handle TheirStack webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.