Any RSS feed, POSTed to your code as JSON.
You wanted one thing: to know when that feed has something new. What you were about to write was a poller, an XML parser, a seen-items table, a cron entry, and the bug where it re-notifies everything after a deploy. Point emit at the feed instead and every new item arrives at your endpoint as clean JSON.
One call
curl -X POST https://api.rssemit.com/v1/sinks \
-H "Authorization: Bearer $EMIT_KEY" \
-d '{
"kind": "webhook",
"url": "https://yourapp.dev/feed-events",
"feed_url": "https://anyblog.com/rss.xml"
}'
That's the whole setup. The feed can be one you don't own — someone else's blog, a GitHub releases feed, a status page, a subreddit — or a feed emit built for you from a page that never had one.
What lands on your endpoint
POST /feed-events
Content-Type: application/json
X-Emit-Event: sink.item
{
"event": "item",
"sink_id": "9c1f…",
"name": "webhook: anyblog.com",
"item": {
"guid": "https://anyblog.com/posts/shipping-v2",
"title": "Shipping v2",
"url": "https://anyblog.com/posts/shipping-v2",
"summary": "The rewrite is done and here is what changed…",
"published_at": "2026-07-30T09:12:00+00:00"
}
}
Fixed shape, every time — normalized out of whatever RSS 2.0, Atom, or JSON Feed dialect the publisher happened to emit, with the summary already stripped to text. No feed-parsing library in your stack, and no surprise the day a publisher changes CMS.
Or batch it
A busy feed doesn't need to wake your service forty times a day. Set
"schedule": "daily" (or "weekly", with your own
digest_hour and digest_tz) and emit holds items and sends
one POST at the boundary instead:
X-Emit-Event: sink.digest
{
"event": "digest",
"sink_id": "9c1f…",
"name": "webhook: anyblog.com",
"items": [ { "guid": "…", "title": "…", "url": "…", "summary": "…", "published_at": "…" }, … ]
}
The parts you were going to get wrong
- No back-catalog blast. The first poll records a silent baseline, so connecting a feed with ten years of history delivers nothing. You get what's new from that moment on.
- Deduplicated by guid. Publishers edit and republish items constantly, and plenty of feeds reorder themselves. You get each item once.
- Failures retry, they don't vanish. Any 2xx counts as delivered. An error status, a timeout, an unreachable host — the item stays unconsumed and is retried on the next cycle. The dashboard shows the last error verbatim.
-
Conditional GET. Feeds are polled every five minutes with
If-None-MatchandIf-Modified-Since, so a quiet feed costs the publisher a 304 rather than a full download. - Your URL is validated. Endpoints are checked against loopback, private, and link-local ranges before anything is sent.
Worth knowing: per-item mode sends up to 10 items per polling cycle, so a feed that dumps fifty posts at once drains over the following cycles rather than stampeding your endpoint. A digest holds up to 200 items between boundaries.
See it before you trust it
POST /v1/sinks/{id}/test sends one real delivery — the newest item already in
the feed, or content you pass in — and returns the exact body it posted. In the dashboard,
the test button renders that body inline, so you can diff it against your
handler before you write a line of it.
This isn't the signed-webhook thing
emit has two kinds of outbound HTTP, and they answer different questions. JSON broadcasts — this page — carry a feed's items, from any feed URL, with optional digest batching. Account-event webhooks carry things that happened in your emit account (a subscriber joined, a broadcast went out); those are HMAC-signed, retried with backoff, and have a per-endpoint delivery log. Most people who arrive here want the first one.
Pricing
Free. Polling feeds and pushing them out — webhooks, Slack, Discord, or the feed itself — costs nothing. Credits meter only email delivery (1 credit per email, $1.60 per 1,000) and optional LLM work (1 credit per 5 items judged, 1 per summary), from one prepaid balance. A webhook sink that never sends an email never spends a credit.
Where it gets interesting
The feed you point at doesn't have to be someone's raw output. Put a filtered feed in front of it — a plain-language prompt over forty sources, LLM-judged, deduplicated — and your endpoint stops receiving everything and starts receiving only what matched. Same payload, far less of it.