Skip to main content

Welcome to the Webhooks API

Webhooks push real-time signal data to your own HTTPS endpoint the moment events happen — funding rounds, acquisitions, job changes, new hires, and newly discovered companies. Instead of polling, your system receives a signed POST with the full event payload. Webhooks are a first-class API resource: you can create, list, retrieve, update, rotate the signing secret of, test, and delete them entirely over REST, plus list delivery attempts and replay failed ones. Anything you do in the dashboard for webhooks is doable via the API now.

Create Webhook

Subscribe an endpoint to event types and receive the signing secret.

List Webhooks

Retrieve every active webhook for your team.

Send Test Event

Fire a signed system.test event without waiting for a real signal.

List Deliveries

Inspect recent delivery attempts and their status.

Key Features

  • API-first: Full CRUD plus test, delivery listing, and retry — no dashboard required.
  • Signed payloads: Every delivery carries an HMAC-SHA256 signature so you can verify authenticity.
  • Secret rotation: Rotate the signing secret in place without recreating the webhook.
  • Event subscriptions: Subscribe each webhook to exactly the event types you care about.
  • Delivery visibility: List delivery attempts with status, response code, and stored response body.
  • Manual retry: Replay a failed delivery; it reuses the original event_id so you can dedupe.
  • Dashboard parity: The same webhook is fully manageable from the dashboard and the API.

Authentication

Every request needs an API key (ff_live_…) belonging to a team with an active or trialing subscription. Pass it as a Bearer token in the Authorization header. Get your key from the dashboard at trysignalbase.com/workspace/api.
curl -X POST "https://www.trysignalbase.com/api/v2/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint_url": "https://example.com/hooks/signalbase",
    "event_types": ["funding.created", "hiring.created"],
    "filters": { "countries": "US,GB" }
  }'
The base URL for all V2 endpoints is https://www.trysignalbase.com/api/v2. A key is bound to one team; all webhooks it creates belong to that team and are visible to every team member. Request bodies may use snake_case (canonical) or camelCase (endpointUrl, eventTypes, rotateSecret).

Response Envelope

Every endpoint returns a consistent JSON envelope. Success:
{
  "success": true,
  "data": {},
  "pagination": {},
  "meta": { "endpoint": "webhooks.create", "creditsUsed": 0 }
}
pagination is present only on list endpoints. meta.creditsUsed reflects the credits this request consumed. Error:
{ "success": false, "error": "Webhook not found", "code": "not_found" }

The Webhook Resource

{
  "id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "endpoint_url": "https://example.com/hooks/signalbase",
  "event_types": ["funding.created", "hiring.created"],
  "filters": { "countries": "US,GB" },
  "active": true,
  "created_at": "2026-05-29T10:30:00.000Z",
  "updated_at": "2026-05-29T10:30:00.000Z",
  "last_modified_by": "user_2a9f8c7b6d5e4f3a",
  "last_modified_via": "api",
  "secret": "3f9a1c7e-2b4d-4e6a-9f1b-8c2d3e4f5a6b",
  "secret_preview": "…5a6b",
  "stats": {
    "total_deliveries": 42,
    "successful_deliveries": 40,
    "failed_deliveries": 2,
    "last_success_at": "2026-05-29T12:00:00.000Z",
    "last_failure_at": "2026-05-28T09:00:00.000Z",
    "consecutive_failures": 0
  }
}
FieldTypeDescription
idstringUnique webhook ID (UUID).
endpoint_urlstringHTTPS endpoint that receives deliveries.
event_typesstring[]Event types this webhook is subscribed to.
filtersobjectString key/value filters that narrow which events are delivered.
activebooleanfalse for soft-deleted/paused webhooks.
created_at / updated_atstringISO-8601 timestamps.
last_modified_bystring | nullUser ID of the last editor.
last_modified_viastring | null"api" or "dashboard" — where the last change originated.
secretstringFull signing secret. Returned only on create and on rotation.
secret_previewstringEllipsis + last 4 characters of the secret (e.g. …5a6b). Returned on every other read.
statsobjectDelivery counters and last success/failure timestamps.
The full secret is returned exactly once — when you create the webhook, and again only if you rotate it (rotate_secret: true). Store it securely on receipt. Every other read returns only secret_preview; the full secret cannot be retrieved again via the API.

Event Types

Subscribe to these in event_types when you create or update a webhook:
Event typeTriggered when
funding.createdA tracked company raises a funding round.
acquisition.createdA tracked company is acquired or makes an acquisition.
job_change.createdA monitored person changes jobs.
hiring.createdA tracked company opens a relevant role.
new_company.createdA new company matching your filters is discovered.
Two additional event types may appear on the receiving side but are not regular subscriptions:
  • system.test — delivered only by the Send Test Event endpoint. It is accepted in event_types for convenience but never fires automatically.
  • dashboard.push — delivered when a teammate uses the dashboard Push button to send selected companies/investors/contacts to a webhook. Personal data is GDPR-masked (last names masked, personal emails filtered) before delivery.

Filters

filters is an optional string-to-string map that narrows which events are delivered, applied to every *.created event the subscription is registered for. Only the keys below are recognized — any other key is silently ignored (it does not narrow anything), so the key names matter. All values are strings.
KeyApplies toValue formatMatches when
countriesall signal typesComma-separated ISO 3166-1 alpha-2 codes, e.g. "US,GB"the company HQ country is in the list
categoriesall signal typesPipe-separated industry labels, e.g. "Software Development|Financial Services"the company industry/categories match (case-insensitive)
searchall signal typesFree text, e.g. "fintech"the substring appears in the company name or industry
teamSizeall signal typesComma-separated ranges min-max or 1000-plus, e.g. "11-50,51-200"the company employee count falls in any range
dateFrom / dateToall signal typesISO-8601 date, e.g. "2026-01-01"the signal’s occurredAt is within the range
fundingfunding.created onlyComma-separated round types, e.g. "seed,series a"the round type matches (case-insensitive)
roundFlavorfunding.created onlyComma-separated flavors: bridge, extension, secondarythe round flavor matches
fundingAmountfunding.created onlyComma-separated amount ranges, e.g. "1m-5m,5m-20m" (also ">$100m")the funding amount falls in any range
acquisitionAmountacquisition.created onlyComma-separated amount ranges, e.g. "10m-50m"the acquisition amount falls in any range
healthScorefunding.created onlyA single number 0100, e.g. "40"the round’s confidence score is ≥ that value
Important behaviours:
  • Use funding (not round) to filter funding rounds. The webhook filter key names differ from the REST query params — e.g. the round-type key here is funding, and amount uses fundingAmount/acquisitionAmount ranges rather than amount_min/amount_max. A wrong key like { "round": "seed" } is ignored and you’ll receive the full firehose.
  • Multiple keys combine with AND — an event must satisfy every filter to be delivered.
  • Type-specific keys drop other types. If a subscription listens to several event types and you set a funding-only key (funding, roundFlavor, fundingAmount, healthScore), non-funding events (e.g. hiring.created) won’t match and won’t be delivered. Prefer one webhook per event type when using type-specific filters.
Example — only US/GB seed & Series A rounds above $1M:
{
  "countries": "US,GB",
  "funding": "seed,series a",
  "fundingAmount": "1m-100m"
}

Credits & Limits

ItemValue
Create a webhookFree (0 credits)
Successful delivery (including a successful retry)1 API credit
List / retrieve / update / delete / test / list deliveriesFree (0 credits)
Max active webhooks per team10
Endpoint URL max length500 characters
Allowed protocolsHTTP or HTTPS (HTTPS strongly recommended)
Stored response bodyFirst 1000 characters per delivery
Default rate limit60 requests/minute per endpoint per team (higher limits available contractually)
Webhook management, reads, and creation are all free; only successful deliveries (including a successful manual retry) consume credits. If your team’s API credit balance reaches zero, deliveries stop.

The Delivery Your Endpoint Receives

Each delivery is an HTTP POST with Content-Type: application/json and User-Agent: Signalbase-Webhooks/1.0.
HeaderValue
X-Webhook-EventThe event type (e.g. funding.created).
X-Webhook-IDThe event ID — stable across retries, safe for idempotency.
X-Webhook-TimestampUnix timestamp in seconds.
X-Webhook-SignatureHex-encoded HMAC-SHA256 of the raw request body. No sha256= prefix — the bare hex digest.
Body envelope — every delivery has the same four top-level keys. data holds the full signal object, whose shape depends on event_type (see Payload by event type below).
{
  "data": { "...": "full signal object — shape depends on event_type" },
  "event_id": "1f2e3d4c-5b6a-4790-8a1b-2c3d4e5f6071",
  "event_type": "funding.created",
  "timestamp": "2026-05-29T10:30:00.000Z"
}
The delivered body is canonical JSON with object keys sorted alphabetically at every level (note the data, event_id, event_type, timestamp order above). The signature is computed over those exact bytes — so always verify against the raw request body you received, never a re-serialized copy.

Payload by event type

Every signal payload shares a common envelope inside data:
  • signal{ id, type, externalId, occurredAt, discoveredAt, active }
  • company — the full company object (or null for some events); headquartersCountry is an ISO-style country code
  • employees — array of { id, name, title, linkedinUrl, email } (may be empty)
  • sources — array of { url, sourceType, author, title, content, validationScore, validationReasoning, publishedAt }
Each event type then adds one type-specific block. Monetary amount fields are whole currency units (integers, not cents); currency tells you which currency, as an ISO 4217 code (e.g. USD, EUR). See Amounts & Currency.
{
  "data": {
    "signal": {
      "id": "a3f1c2d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "type": "funding_round",
      "externalId": "fund_abc123",
      "occurredAt": "2026-05-29T00:00:00.000Z",
      "discoveredAt": "2026-05-29T08:15:00.000Z",
      "active": true
    },
    "company": {
      "id": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
      "name": "Acme AI",
      "slug": "acme-ai",
      "description": "Realtime fraud detection for fintechs.",
      "website": "https://acme.ai",
      "linkedinUrl": "https://www.linkedin.com/company/acme-ai",
      "twitterUrl": "https://x.com/acmeai",
      "logoUrl": "https://logo.clearbit.com/acme.ai",
      "industry": "Software Development",
      "subcategory": "fraud-detection",
      "foundedYear": 2021,
      "headquartersCountry": "US",
      "employeeCount": 85,
      "categories": "ai,fintech",
      "keywords": "fraud,risk,ml",
      "specialties": "fraud detection,risk scoring"
    },
    "employees": [],
    "sources": [
      {
        "url": "https://techcrunch.com/2026/05/29/acme-ai-series-b",
        "sourceType": "news_article",
        "author": "Jane Reporter",
        "title": "Acme AI raises $40M Series B",
        "content": "Acme AI today announced...",
        "validationScore": 0.92,
        "validationReasoning": "Multiple corroborating sources.",
        "publishedAt": "2026-05-29T07:00:00.000Z"
      }
    ],
    "fundingRound": {
      "roundType": "series b",
      "roundFlavor": null,
      "amount": 40000000,
      "currency": "USD",
      "announcedDate": "2026-05-29",
      "confidenceScore": 0.92,
      "verificationStatus": "verified",
      "investors": [
        { "name": "Sequoia Capital", "type": "vc", "leadInvestor": true },
        { "name": "Y Combinator", "type": "accelerator", "leadInvestor": false }
      ]
    }
  },
  "event_id": "1f2e3d4c-5b6a-4790-8a1b-2c3d4e5f6071",
  "event_type": "funding.created",
  "timestamp": "2026-05-29T08:15:00.000Z"
}
Person names in job_change.created payloads are GDPR-masked (first name + last initial, e.g. "Jordan M."), matching the masking applied across the REST API. Use personLinkedinUrl as the stable identifier for a person.
For dashboard.push, data instead contains companies, investors, and contacts arrays (contacts GDPR-masked) plus a meta block describing the push.

Verifying the Signature

The signature is HMAC-SHA256(raw_body, secret), hex-encoded, sent in X-Webhook-Signature. The secret is the value returned when you created (or last rotated) the webhook.
Verify against the exact raw bytes of the request body. Parsing the JSON and re-serializing it can change whitespace or key order, which changes the bytes and breaks the HMAC. Capture the raw body before any JSON parsing.
import express from "express";
import crypto from "node:crypto";

const app = express();
const WEBHOOK_SECRET = "your-webhook-secret";

// express.raw keeps req.body as the raw Buffer so the HMAC matches exactly.
app.post(
  "/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sent = req.get("X-Webhook-Signature") ?? "";
    const expected = crypto
      .createHmac("sha256", WEBHOOK_SECRET)
      .update(req.body) // raw bytes
      .digest("hex");

    const a = Buffer.from(expected);
    const b = Buffer.from(sent);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).send("bad signature");
    }

    const event = JSON.parse(req.body.toString("utf8"));
    // ...handle event, dedupe on event.event_id...
    res.json({ ok: true });
  },
);

app.listen(3000);

Delivery & Retry Contract

  • Success = any HTTP 2xx from your endpoint. Any other status, or a network error, is a failure (a network error is recorded with response_status: 0).
  • One attempt per event. The current release does not automatically retry failed deliveries on a schedule. Build idempotent handlers and reconcile gaps yourself.
  • Manual retry is available: list failed deliveries with List Deliveries (?status=failed) and replay them with Retry Delivery.
  • Idempotency: event_id (also sent as X-Webhook-ID) is stable across the original delivery and any retries, so you can safely dedupe.
  • Response storage: the first 1000 characters of your response body are stored per delivery for debugging.
  • Respond quickly — acknowledge with 2xx, then process asynchronously.

Full Lifecycle Over the API

A vendor can onboard end-to-end without opening the dashboard:
# 1. Create (capture the secret from the response — shown only once)
curl -X POST "https://www.trysignalbase.com/api/v2/webhooks" \
  -H "Authorization: Bearer $SB_KEY" -H "Content-Type: application/json" \
  -d '{ "endpoint_url": "https://example.com/hook", "event_types": ["funding.created"] }'

# 2. List
curl "https://www.trysignalbase.com/api/v2/webhooks" -H "Authorization: Bearer $SB_KEY"

# 3. Fire a test event and verify the signature on your endpoint
curl -X POST "https://www.trysignalbase.com/api/v2/webhooks/$ID/test" \
  -H "Authorization: Bearer $SB_KEY"

# 4. Inspect failed deliveries, then retry one
curl "https://www.trysignalbase.com/api/v2/webhooks/$ID/deliveries?status=failed" \
  -H "Authorization: Bearer $SB_KEY"
curl -X POST "https://www.trysignalbase.com/api/v2/webhooks/$ID/deliveries/$DELIVERY_ID/retry" \
  -H "Authorization: Bearer $SB_KEY"

# 5. Delete (soft-delete)
curl -X DELETE "https://www.trysignalbase.com/api/v2/webhooks/$ID" \
  -H "Authorization: Bearer $SB_KEY"

Dashboard Parity & Migration

Everything above is also available without code under Workspace → Webhooks in the dashboard. Because the dashboard and the REST API share one code path, a webhook created in the UI is fully manageable via the API and vice versa.
  • Webhooks are editable in place. To change a URL or event list, use Update Webhook (or the dashboard Edit button) — you no longer need to delete and recreate. The signing secret is preserved across edits unless you pass rotate_secret: true.
  • Retrieve existing config: vendors who configured webhooks through the dashboard can list and manage them immediately via List Webhooks — no migration step required. Only secret_preview is exposed for pre-existing webhooks; rotate the secret if you need a fresh full value.

Error Handling

Errors return a non-2xx status with { success: false, error, code }.
HTTPcodeMeaning
400bad_requestInvalid body, invalid endpoint URL, invalid event type, or the 10-webhook limit was reached.
401invalid_api_keyMissing or invalid API key.
403subscription_expiredThe key’s team has no active subscription.
404not_foundWebhook or delivery not found (or not owned by your team).
409conflictRetrying a delivery that already succeeded.
429rate_limitedRate limit exceeded. Includes a retryAfter field and X-RateLimit-* headers.
500internal_server_error / internal_errorUnexpected server error.