Webhooks

TenderSignal can send each alert to an address you choose, as a small JSON message, the moment it happens. That address can belong to Zapier, Make, n8n, Power Automate, Pipedream or your own server, so a new match can become a row in a spreadsheet, a card on a project board or a task for your bid team without anyone copying it across. Webhooks are part of the Pro and Business plans.

Connect a tool in two minutes

Open Settings, then Integrations, and choose Connect on the Webhooks card. Paste the address your tool gives you, pick the events you want and save. Then press Send test event: a sample arrives straight away, so your tool can learn the shape of the data before the first real alert.

  • Zapier: use Webhooks by Zapier as the trigger with the Catch Hook event, and paste its URL.
  • Make: add a Webhooks module, choose Custom webhook, and paste its address.
  • n8n: add a Webhook node set to POST and use its production URL.
  • Power Automate: start a flow with When an HTTP request is received and paste the HTTP POST URL.

What we send

Every alert is an HTTPS POST with a JSON body. The body is the event itself: an id, the event type, when it happened, the tender it is about (where there is one) and a data object whose fields depend on the type. Every date carries its UK offset, for example 2026-10-22T12:00:00+01:00, so a noon deadline never shows up as the evening before. Contract values are in pounds, as whole numbers. Three headers come with each request:

  • X-TenderSignal-Signature: sha256= followed by the HMAC-SHA256 of the raw body, keyed with your connection's signing secret.
  • X-TenderSignal-Event: the event type, such as watch.deadline, so you can route without parsing the body.
  • X-TenderSignal-Delivery: a unique id for this delivery. A retry of the same delivery keeps the same id, so you can ignore repeats.

Check the signature

Your signing secret is shown when you create the connection and can be shown again from the same card. Compute the HMAC over the exact bytes you received, before parsing them as JSON, and compare it with the header using a constant-time comparison. Reject anything that does not match.

import crypto from "crypto";

// rawBody: the exact bytes you received, BEFORE any JSON parsing.
export function verify(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return signatureHeader.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}
import hashlib, hmac

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature_header, expected)

Retries, failures and limits

Answer with any 2xx status within 10 seconds and the delivery counts as done. If your endpoint times out, cannot be reached, or answers 408, 429 or a 5xx status, we try again after 1 minute, 10 minutes, 1 hour and 6 hours, then give up. Any other answer, including a redirect, is treated as a refusal and is not retried. After three deliveries in a row fail, the connection is marked as failing in your settings and named in your next alert email. Your settings page shows the last 20 deliveries for each connection, with the status code and the first 500 characters of any error.

Each connection receives at most 50 events a day, and new matches arrive as one message per saved search per day rather than one per tender. If a connection reaches its daily limit we send a single notice and hold back the rest of that day's events. Addresses must use HTTPS on the standard port and must point at the public internet; private and internal network addresses are refused when you save and again on every send.

Events

The examples below are produced by the same code that builds real deliveries, shown as a Business account sees them. Pro accounts receive the Pro events below, without the Business-only fields noted under each one. A new-matches message lists at most 10 tenders.

matches.daily

One message per saved search per day when it has new matches. Plan: Pro and Business.

{
  "id": "evt_example_matches_daily",
  "type": "matches.daily",
  "occurred_at": "2026-10-01T09:00:00+01:00",
  "tender": null,
  "data": {
    "search_id": 812,
    "search_name": "Grounds maintenance",
    "count": 3,
    "tenders": [
      {
        "id": 66356124,
        "title": "Grounds maintenance and arboriculture services",
        "buyer": "Torbay Council",
        "value": {
          "min": null,
          "max": 65000,
          "currency": "GBP"
        },
        "deadline": "2026-10-22T12:00:00+01:00",
        "url": "https://www.tendersignal.co.uk/tender/66356124",
        "buyer_contact": {
          "name": "Procurement Team",
          "email": "[email protected]",
          "phone": null
        }
      },
      {
        "id": 66356130,
        "title": "Tree surgery framework",
        "buyer": "Devon County Council",
        "value": {
          "min": null,
          "max": 65000,
          "currency": "GBP"
        },
        "deadline": "2026-10-22T12:00:00+01:00",
        "url": "https://www.tendersignal.co.uk/tender/66356130",
        "buyer_contact": {
          "name": "Procurement Team",
          "email": "[email protected]",
          "phone": null
        }
      }
    ],
    "url": "https://www.tendersignal.co.uk/saved-searches/812/matches"
  }
}

watch.deadline

When a watched tender is 7 days and 2 days from its deadline. Plan: Pro and Business.

{
  "id": "evt_example_watch_deadline",
  "type": "watch.deadline",
  "occurred_at": "2026-10-01T09:00:00+01:00",
  "tender": {
    "id": 66356124,
    "title": "Grounds maintenance and arboriculture services",
    "buyer": "Torbay Council",
    "value": {
      "min": null,
      "max": 65000,
      "currency": "GBP"
    },
    "deadline": "2026-10-22T12:00:00+01:00",
    "url": "https://www.tendersignal.co.uk/tender/66356124",
    "buyer_contact": {
      "name": "Procurement Team",
      "email": "[email protected]",
      "phone": null
    }
  },
  "data": {
    "days_left": 2
  }
}

renewal.window

Once a week: contracts in your sectors entering their six-month renewal window. Plan: Pro and Business. Buyer contacts and source links in each contract are Business only.

{
  "id": "evt_example_renewal_window",
  "type": "renewal.window",
  "occurred_at": "2026-10-01T09:00:00+01:00",
  "tender": null,
  "data": {
    "count": 1,
    "contracts": [
      {
        "id": 40211,
        "title": "Parks and open spaces maintenance",
        "buyer": "Plymouth City Council",
        "region": "South West",
        "sector": "Facilities and grounds",
        "value_min": 1200000,
        "expected_month": "2027-03",
        "buyer_contact": {
          "name": "Contracts Team",
          "email": "[email protected]",
          "phone": null
        },
        "source_url": "https://www.example.gov.uk/notices/parks-2023"
      }
    ],
    "url": "https://www.tendersignal.co.uk/dashboard"
  }
}

watch.changed

An amendment, a deadline change or a cancellation on a tender you watch. Plan: Pro and Business.

{
  "id": "evt_example_watch_changed",
  "type": "watch.changed",
  "occurred_at": "2026-10-01T09:00:00+01:00",
  "tender": {
    "id": 66356124,
    "title": "Grounds maintenance and arboriculture services",
    "buyer": "Torbay Council",
    "value": {
      "min": null,
      "max": 65000,
      "currency": "GBP"
    },
    "deadline": "2026-10-22T12:00:00+01:00",
    "url": "https://www.tendersignal.co.uk/tender/66356124",
    "buyer_contact": {
      "name": "Procurement Team",
      "email": "[email protected]",
      "phone": null
    }
  },
  "data": {
    "change": "deadline",
    "before": "2026-10-15T12:00:00+01:00",
    "after": "2026-10-22T12:00:00+01:00"
  }
}

competitor.award

A supplier you follow wins a contract. Plan: Business.

{
  "id": "evt_example_competitor_award",
  "type": "competitor.award",
  "occurred_at": "2026-10-01T09:00:00+01:00",
  "tender": {
    "id": 66356124,
    "title": "Grounds maintenance and arboriculture services",
    "buyer": "Torbay Council",
    "value": {
      "min": null,
      "max": 65000,
      "currency": "GBP"
    },
    "deadline": null,
    "url": "https://www.tendersignal.co.uk/tender/66356124",
    "buyer_contact": {
      "name": "Procurement Team",
      "email": "[email protected]",
      "phone": null
    }
  },
  "data": {
    "supplier": "Example Grounds Ltd",
    "value": 62500
  }
}

watch.added

A tender is added to the watchlist (creates or updates the CRM deal). Plan: Business.

{
  "id": "evt_example_watch_added",
  "type": "watch.added",
  "occurred_at": "2026-10-01T09:00:00+01:00",
  "tender": {
    "id": 66356124,
    "title": "Grounds maintenance and arboriculture services",
    "buyer": "Torbay Council",
    "value": {
      "min": null,
      "max": 65000,
      "currency": "GBP"
    },
    "deadline": "2026-10-22T12:00:00+01:00",
    "url": "https://www.tendersignal.co.uk/tender/66356124",
    "buyer_contact": {
      "name": "Procurement Team",
      "email": "[email protected]",
      "phone": null
    }
  },
  "data": {}
}

system.test

Sent when you press Send test event. Safe to ignore in production flows.

{
  "id": "evt_example_test",
  "type": "system.test",
  "occurred_at": "2026-10-01T09:00:00+01:00",
  "tender": null,
  "data": {
    "message": "Test event from TenderSignal. If you can see this, your connection works."
  }
}