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 arenull.
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.
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
Hacker News hn_item.created
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 anaiStep: 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:
Delivery waits for the reading
A keyword with anaiStep 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, eachstring, 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:
[] 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 anX-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):
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 withGET /v1/keywords/{id}/events (free):
{ "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.
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):
{ "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.