Prerequisites
- A Pubrio API key with monitor access
- A publicly accessible HTTPS endpoint (or a test URL from usewebhook.com)
Step 1: Create a Monitor with Webhook Destination
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.
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.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 — usetried_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 secretsignature (a UUID). Reveal it once and keep it on your side:
Verify the HMAC over the exact bytes you received, before parsing the JSON:
Node.js
Webhook Payload Structure
Payloads differ based on the monitor’sdetection_mode:
- Signal First
- Company First
In Each signal entry contains the signal details and the associated enriched companies and people.
signal_first mode, the payload contains a top-level signals array: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
Alongsidejobs, 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:
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:
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
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
Addcloud_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.
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. Setis_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: Nguarantees 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) andmetadata.parent_monitor_log_id(nullon 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 onparent_monitor_log_id || monitor_log_id. A retry replays the first attempt’s payload as it was; sendis_use_original_payload: falseto re-apply today’s rules first (exclusions and both ledgers), in which case a retry with nothing left is skipped with code40085.
Email Destination
For teams that prefer email delivery, setdestination_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
Webhook not receiving payloads
Webhook not receiving payloads
- Verify your endpoint is publicly accessible (not behind a firewall or VPN)
- Ensure it returns a
200status code — other codes are treated as failures - Use the Validate Webhook endpoint to test connectivity
- Check Statistic Logs for error messages and response codes
Monitor paused after failures
Monitor paused after failures
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.Duplicate payloads
Duplicate payloads
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.Payload too large
Payload too large
Reduce
max_records_per_trigger to limit records per delivery. You can also narrow your filters to reduce matching signal volume.
