Skip to main content
Webhooks are the recommended way to receive monitor results. When a monitor triggers, Pubrio sends a POST request with a JSON payload to your configured URL — in real-time.

Prerequisites

  • A Pubrio API key with monitor access
  • A publicly accessible HTTPS endpoint (or a test URL from usewebhook.com)
Quick testing: Use usewebhook.com to generate a free temporary webhook URL. You can inspect every incoming payload without deploying anything.

Step 1: Create a Monitor with Webhook Destination

Response:
The headers object adds custom HTTP headers to every delivery (useful for authentication). The body object adds custom fields to the root of the webhook payload.
Save the signature from the response — you will need it to verify incoming payloads. It is only returned at creation time, via the Signature Reveal endpoint, or from Monitor Lookup with is_signature_reveal: true.

Step 2: Validate Your Webhook Connection

Use the Validate Webhook endpoint to test that your endpoint is reachable. This sends a sample payload with placeholder data — no credits are consumed, no real signals are fetched.
A successful response returns the sample request payload that was sent and the response your endpoint returned — so you can confirm the connection works before going live.

Step 3: Test with Real Data

Once the connection is validated, trigger a real run using the Process Try endpoint. This fetches actual signals and delivers them to your webhook — use tried_at with a recent past date to ensure data is available:
Unlike validate, the try endpoint runs a real scan and consumes credits. Use it to verify real payloads arrive correctly and to get a quick estimate of results before the scheduled scan kicks in.

Step 4: Verify Signatures

Each monitor has a secret signature (a UUID). Reveal it once and keep it on your side:
Every delivery carries two headers: Verify the HMAC over the exact bytes you received, before parsing the JSON:
Node.js
The plain header is kept for receivers that already compare it; prefer the HMAC.

Webhook Payload Structure

Payloads differ based on the monitor’s detection_mode:
In signal_first mode, the payload contains a top-level signals array:
Each signal entry contains the signal details and the associated enriched companies and people.
Custom body fields from destination_config appear at the root level of the payload (e.g., "pipeline": "my-webhook" when configured in your destination).

Expansion Signals

Alongside jobs, news and advertisements, a monitor can watch expansion signals — the dated evidence that a company is entering or growing in a new market. Add expansions to signal_types:
Expansion filters use the Expansion Search vocabulary, not the job/news/ad one — froms and tos for the corridor, plus stages, scopes, momentum, freshness, signal_types, signal_subtypes, signal_strengths, source_types and window_days. Resolve the valid slugs from Expansion Reference.
A webhook delivers expansion signals at the monitor’s record unit. At the default signal unit (signal_first) each entry is one raw signal (HIRE, AD, OFFICE…) whose companies[0] carries the (company, market) card; country_code is the market. Set record_unit: "company" to receive one entry per company with its signals nested. The email digest always groups per company and market.

Cloud Footprint

DNS is the cloud footprint signal — a company standing up cloud or hosting infrastructure in a market. Narrow an expansion monitor to it with signal_types inside the expansion filters:
Every delivery carries evidence_url, a Pubrio page backing the signal. Raw infrastructure detail is never included in the payload. For the same hosts as a plain event stream with provider, region and city filters, use the dedicated cloud_footprints signal type below.

Expansion Signal Payload

Each companies[0] card also carries the stage read: stage_slug, expansion_score, confidence_score, direction, freshness, signal_count, first_signal_at, latest_signal_at and last_transition_at. Field-by-field meanings live in the Expansion Field Reference. To pull the same rows on demand rather than on a trigger, use Expansion Signal Search.

Cloud Footprint Signals

Add cloud_footprints to signal_types to receive one record per host a company stands up in a market outside its home country: a new server on a cloud or hosting provider, or one that moved region or country. It is the same infrastructure evidence the expansion DNS signal reads, delivered as a plain event stream instead of a market stage: every host, in arrival order, with the footprint’s own filters and no stage, score or window.
Each signal carries provider, cloud_region, city, novelty, country_code, country_name, event_date, a localised display_label such as AWS · Frankfurt, and evidence_url. Raw host names and IP addresses are never included. Home-market infrastructure is not in this stream.

Delivery Rules

What one record is

A record is what the caps count and what a delivery entry is. By default it follows the detection mode: signal_first delivers signals (one signals[] entry per job, ad, article or expansion signal), company_first delivers companies (one companies[] entry per company with its signals nested). Set record_unit to choose explicitly, for example record_unit: "company" on a signal_first advertisements monitor delivers one entry per advertiser instead of one per ad.

Excluding companies

excluded_companies (plus excluded_domains and excluded_linkedin_urls, resolved the same way as companies) is a never-deliver list: an excluded company is dropped before enrichment and billing in both detection modes, and a company_first watch list skips it while it stays on the list. Both the watch list (companies, domains, linkedin_urls) and the exclusion list share one limit of 100,000 companies per monitor, enough for a CRM export. A list above the limit is rejected, never cut. Update Monitor replaces a list you send and leaves one you omit; send companies_mode or excluded_companies_mode as add or remove to change only the entries you send, which is how a CRM sync keeps a list current without re-sending it. Create, Update and Try answer with metadata.unresolved, naming every domain or LinkedIn URL no company answered to, per list, so nothing is dropped silently.
To exclude whole industries instead of named companies, use company_filters with the Company Search exclusion keys: exclude_vertical_categories, exclude_vertical_sub_categories, exclude_verticals and exclude_technologies. They apply to every signal type, advertisements included.

Caps

A list of any size reads back page by page: Company Search with company_groups: [source_group_id] (or excluded_group_id), both returned by Lookup Monitor, which also returns the full lists.

Freshest first

By default a monitor delivers in arrival order, oldest first, and a cap carries the rest over, so a day’s backlog is worked through before newer signals. Set is_newest_first: true to flip that: each run delivers the newest matching records, and whatever a cap leaves out is skipped for good, never queued. This holds for every signal type and for a monitor resuming after a pause or an exhausted daily budget.

Deduplication and re-delivery

  • A signal is never delivered twice. Every successful delivery logs the keys of the signals it carried, and every run drops any candidate already in that log, for all signal types. Jobs, news and advertisements also keep a per-type cursor, so a delivered signal is not even fetched again.
  • company_dedupe_days: N guarantees a company appears in at most one delivery per N days across all the monitor’s signal types; its later signals inside the window are skipped.
  • Every payload carries metadata.monitor_log_id (the Statistic Logs row of that attempt) and metadata.parent_monitor_log_id (null on a first attempt; on a retry, automatic or via Process Retry, the first attempt’s id). The same signals can only reach you again through such a retry, so deduplicate on parent_monitor_log_id || monitor_log_id. A retry replays the first attempt’s payload as it was; send is_use_original_payload: false to re-apply today’s rules first (exclusions and both ledgers), in which case a retry with nothing left is skipped with code 40085.

Email Destination

For teams that prefer email delivery, set destination_type to "email":
Pubrio supports white-label email delivery for agencies and teams. Get in touch to learn about customizing the sender domain and branding.

Troubleshooting

  • Verify your endpoint is publicly accessible (not behind a firewall or VPN)
  • Ensure it returns a 200 status code — other codes are treated as failures
  • Use the Validate Webhook endpoint to test connectivity
  • Check Statistic Logs for error messages and response codes
If your webhook returns non-200 codes consistently, the monitor pauses after reaching max_failure_trigger consecutive failures. Fix the issue and reactivate via Update Monitor.
If a delivery fails and retries are configured, you may receive the same payload multiple times. Use triggered_at or the log ID to deduplicate on your end.
Reduce max_records_per_trigger to limit records per delivery. You can also narrow your filters to reduce matching signal volume.
Last modified on October 8, 2026