Skip to main content
Give a keyword a webhookUrl, on POST /v1/keywords or later with PATCH /v1/keywords/{id}. When one of its Sources catches a match, Nephia POSTs an event payload to your endpoint. webhookUrl is optional. A keyword without one polls, bills and records its matches exactly the same; it simply pushes nothing. Read those matches with GET /v1/keywords/{id}/results or GET /v1/mentions?keyword={id}, both free.

Event types

Payload envelope

Every event uses the same shape. Unused fields are null. keywordId is the keyword the match belongs to, and source is the Source that caught it (x, reddit, ai_answers, …). Route on those two and on type. ai is the agent step’s reading of the event. It is null unless the keyword has an aiStep, see Agent step below. analysis is the only optional key in the body: it is present when something read the item, and absent otherwise — see The analysis key below.

Two dates, and which is which

occurredAt is when Nephia collected the item — the instant the tick that caught it ran. publishedAt is when the platform says the item was published. They are usually minutes apart and sometimes years. A keyword polling every 15 minutes that meets a two-year-old Reddit thread on today’s page delivers it with today’s occurredAt and a 2024 publishedAt. Use occurredAt to order or de-duplicate a stream you are syncing, and to filter with since and until — those read occurredAt, so nothing you have already been delivered can arrive behind you. Use publishedAt to answer “when was this said”: grouping mentions by day, charting them over time, deciding whether something is fresh. publishedAt is null when the source gave no real date. Two cases: an AI answer has no publication date at all (it is generated the moment we ask, so its occurredAt is its date), and a YouTube video caught through search reports a relative age — “4 years ago” — which we will not turn into a timestamp we cannot stand behind. Fall back to occurredAt when you need a date for every item.

The analysis key

A keyword with analysis turned on (sentimentEnabled) delivers one extra key, after ai:
Both are read on one pass, so turning analysis on costs one charge and answers both. null inside the object means not read — never “we looked and found nothing”. That verdict has its own value in each vocabulary: neutral for an item that takes no side, other for an item with no recognisable intent. The key is absent, not null, on an event nothing classified. Read it as event.analysis?.intent rather than branching on the key’s existence for anything else. Every other key in the body is always present, and this one is the exception on purpose: an object of two nulls on every delivery would say nothing. The same object comes back from GET /v1/keywords/{id}/events, so a replay and a delivery agree field for field.

X tweet.created

Vinted listing.price_changed

Reddit post.created

Reddit reddit_comment.created

A comment travels in the same post object a submission does, told apart by kind. title is the parent submission’s title, selftext is what the comment said, and id carries Reddit’s t1_ prefix.
A consumer subscribed to post.created never receives one of these: a comment has no submission’s shape to offer it. See Reddit.

Youtube video.created

Bluesky bluesky_post.created

See Bluesky.

Hacker News hn_item.created

On a comment, title is the parent story’s title and url is null — hnUrl is where the comment is read. See Hacker News.

RSS article.created

summary is text, never HTML. See RSS & News.

AI answers answer.changed

mentioned and cited are different questions and a brand can be either without the other — see AI answers. The same shape arrives on answer.created, term.cited and term.uncited; only type differs, and on a first run previous is null.

Agent step

A keyword can carry an aiStep: one instruction and one flat schema of your own fields, applied to every event it emits. The answer arrives on the event, in the ai block. Set it on POST /v1/keywords or with a patch:
Events from that keyword carry the reading:

Delivery waits for the reading

A keyword with an aiStep holds its delivery until the step answers, up to 120 seconds. You receive one message per event, with ai filled in, never a first message without the reading and a second one with it. The event is never lost. If the step times out or fails, the event is delivered anyway with ai.output: null and ai.status set to timeout or failed. Treat a missing reading as “no answer”, not as an error to retry: the event itself is complete. Keywords without an aiStep are unaffected: they deliver immediately, with ai: null.

Schema and rates

The schema is a flat object of 1–12 fields, each string, number, boolean or enum. Every field can answer null — that is a real answer, not an error. See AI for the full subset and the credit rate.

Keyword events

A keyword’s webhook can also be told about the keyword itself: an alert fired, the weekly report is ready, the keyword paused or resumed, a delivery channel it feeds was switched off. An agent or a scenario can then react to “this keyword stopped” without polling for it. These are opt-in, per keyword and per type. webhookEvents is empty by default, and the webhook then receives item events alone, exactly as before. Set it when you create or update a keyword:
Send [] to opt back out of all of them. The keyword needs a webhookUrl: the events go to the same URL as its items. The envelope is smaller than an item event’s. Route on type: every item type names an entity (tweet.created), every keyword-level type starts with keyword. or channel..
summary is null when no model wrote one. The counts stand on their own. A channel’s address or webhook URL is never part of channel.disabled. Deduplicate on id: a retry delivers the same id. The signature and the retries are the ones described below, with the same secret as your item events.

Signature verification

Each delivery includes an X-Signature header of the form t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw JSON body>" using your account’s webhook signing secret. The timestamp prevents replay attacks: reject deliveries whose t is more than 5 minutes from now. Your signing secret is per-account — find it on the API Keys page of the dashboard. With the SDK (recommended):
Manual verification:

Retries

Failed deliveries (non-2xx or network error) retry with backoff: immediate, +30s, +120s (3 attempts total).
In n8n, the official Nephia Trigger does all of this for you: it creates and deletes the keyword, verifies the signature, and drops the repeats, with a poll mode for instances that are not reachable from the internet.

Replay events

To catch up after your endpoint was down, replay what the keyword emitted with GET /v1/keywords/{id}/events (free):
The answer is { "events": [...] }, newest first, across every search of the keyword, the ones it has since replaced included. Each event is the body the webhook delivered for it, key for key: id, type, keywordId, source, the entity keys, previousPrice, occurredAt, publishedAt, ai, and analysis when something read the item. A handler written for the webhook reads a replay unchanged, and the event id is the same in both, so de-duplicating on it is enough.
This is the raw read. GET /v1/keywords/{id}/results and GET /v1/mentions?keyword={id} answer the same matches as mentions (a title, a body, a bucket, a reading), which is what you want for a screen or a report; /events is what you want when your code already speaks the webhook.

Activity

When a keyword goes quiet, or a delivery never arrived, GET /v1/keywords/{id}/activity says what happened underneath (free):
The answer is { "activity": [...] }, newest first. Each entry carries id, source, type, occurredAt, eventsEmitted, creditsCharged, eventId, statusCode and message. It takes the same since, limit and source parameters as /events, and since defaults to the last 7 days.
Events are kept for 90 days. A since older than that returns what is left, not an error: the rest has aged out. The window is the same for every plan and every source.Ninety days is far longer than a replay needs: a delivery that fails is retried for 48 hours, and a since you set from your own last-seen event is usually minutes or hours behind. If you need events beyond that, store them on receipt: the webhook body and the replay body are the same envelope, so nothing is lost by writing the one you are already handed.Retention is not the only way events end. A replay still reaches the events of a retired keyword (DELETE /v1/keywords/{id} without purge stops the polling and keeps the record) but not those of a purged one, which DELETE /v1/keywords/{id}?purge=true deletes outright. If you replay against a keyword somebody may delete, store what you receive.