<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Dev Feed Dispatch | DevRSS.com</title>
<link>https://devrss.com/blog/</link>
<description>Developer RSS feeds, RSS APIs, monitoring, WebSub, validation, release feeds, and syndication architecture.</description>
<language>en-us</language>
<lastBuildDate>Fri, 23 Oct 2026 12:00:00 GMT</lastBuildDate>
<atom:link href="https://devrss.com/rss.xml" rel="self" type="application/rss+xml"/>
<image><url>https://devrss.com/assets/images/favicon.png</url><title>DevRSS.com</title><link>https://devrss.com/</link><width>256</width><height>256</height></image>
<item>
<title>Developer RSS Feeds</title>
<link>https://devrss.com/feeds/</link>
<guid isPermaLink="true">https://devrss.com/feeds/</guid>
<pubDate>Fri, 23 Oct 2026 12:00:00 GMT</pubDate>
<description><![CDATA[Architecture and workflows for engineering blogs, changelogs, release notes, advisories, podcasts, and custom developer feeds.]]></description>
<content:encoded><![CDATA[<p>Architecture and workflows for engineering blogs, changelogs, release notes, advisories, podcasts, and custom developer feeds.</p><p>Continue at <a href="https://devrss.com/feeds/">Developer RSS Feeds</a>.</p>]]></content:encoded>
</item>
<item>
<title>RSS API &amp; RSS to JSON</title>
<link>https://devrss.com/api/</link>
<guid isPermaLink="true">https://devrss.com/api/</guid>
<pubDate>Fri, 23 Oct 2026 12:00:00 GMT</pubDate>
<description><![CDATA[A versioned API model for guarded fetching, deterministic normalization, freshness, warnings, history, and structured errors.]]></description>
<content:encoded><![CDATA[<p>A versioned API model for guarded fetching, deterministic normalization, freshness, warnings, history, and structured errors.</p><p>Continue at <a href="https://devrss.com/api/">RSS API &amp; RSS to JSON</a>.</p>]]></content:encoded>
</item>
<item>
<title>Feed Monitoring</title>
<link>https://devrss.com/monitoring/</link>
<guid isPermaLink="true">https://devrss.com/monitoring/</guid>
<pubDate>Fri, 23 Oct 2026 12:00:00 GMT</pubDate>
<description><![CDATA[Monitoring patterns for reachability, validity, source freshness, identity, scheduling, and delivery.]]></description>
<content:encoded><![CDATA[<p>Monitoring patterns for reachability, validity, source freshness, identity, scheduling, and delivery.</p><p>Continue at <a href="https://devrss.com/monitoring/">Feed Monitoring</a>.</p>]]></content:encoded>
</item>
<item>
<title>Feed Syndication</title>
<link>https://devrss.com/syndication/</link>
<guid isPermaLink="true">https://devrss.com/syndication/</guid>
<pubDate>Fri, 23 Oct 2026 12:00:00 GMT</pubDate>
<description><![CDATA[WebSub, signed webhooks, custom feeds, API streams, digests, archives, retries, and replay.]]></description>
<content:encoded><![CDATA[<p>WebSub, signed webhooks, custom feeds, API streams, digests, archives, retries, and replay.</p><p>Continue at <a href="https://devrss.com/syndication/">Feed Syndication</a>.</p>]]></content:encoded>
</item>
<item>
<title>About DevRSS.com</title>
<link>https://devrss.com/about/</link>
<guid isPermaLink="true">https://devrss.com/about/</guid>
<pubDate>Fri, 23 Oct 2026 12:00:00 GMT</pubDate>
<description><![CDATA[Principles for portable, safe, observable, and replayable developer feed systems.]]></description>
<content:encoded><![CDATA[<p>Principles for portable, safe, observable, and replayable developer feed systems.</p><p>Continue at <a href="https://devrss.com/about/">About DevRSS.com</a>.</p>]]></content:encoded>
</item>
<item>
<title>Designing a B2B Feed API for Multi-Tenant Syndication</title>
<link>https://devrss.com/blog/b2b-feed-api-design/</link>
<guid isPermaLink="true">https://devrss.com/blog/b2b-feed-api-design/</guid>
<pubDate>Fri, 02 Oct 2026 12:00:00 GMT</pubDate>
<category>APIs &amp; Automation</category>
<description><![CDATA[Plan tenant isolation, source governance, pagination, webhooks, analytics, retention, and service levels for a commercial feed platform.]]></description>
<content:encoded><![CDATA[
<p>A B2B feed platform is more than a parser exposed behind an API key. Customers expect isolated data, predictable freshness, searchable history, delivery guarantees, auditability, and controls for the sources they are allowed to follow. Publishers expect respectful fetching and accurate attribution. Operators need to understand cost and failures across tenants without exposing one customer's activity to another.</p>
<p>The architecture becomes manageable when source ingestion, canonical content, tenant subscriptions, and delivery are separate domains. A public source can be fetched once, normalized once, and referenced by many authorized subscriptions. Tenant rules then determine which items are visible, how long they are retained, and where events are delivered. This separation supports scale without turning every feature into a custom pipeline.</p>

<h2>Define the core resources</h2>
<p>Useful API resources include sources, source observations, entries, subscriptions, destinations, delivery attempts, filters, and tenants. A source identifies the external feed and its current canonical URL. An observation records a fetch and its result. An entry is a normalized content record. A subscription connects a tenant to a source with filtering and retention policy. A destination describes a webhook or other delivery channel.</p>
<p>Give every resource an opaque stable ID and include timestamps for creation and meaningful updates. Keep external publisher identifiers as attributes, not primary API keys. Expose links between resources explicitly so a customer can trace an entry back to its source and a delivery back to the subscription rule that produced it. Avoid endpoints whose behavior changes based on hidden account configuration.</p>

<h2>Enforce tenant isolation at the data layer</h2>
<p>Every tenant-owned row and event should carry a tenant identifier that is enforced in queries, queues, caches, and logs. Authorization belongs in a shared policy layer, but database constraints and partitioning provide defense in depth. Do not trust a client-supplied tenant ID; derive it from the authenticated principal. Test cross-tenant access with negative integration tests for every resource type.</p>
<p>Shared public-source data needs a clear boundary. The raw fetched document and canonical entry can be global, while subscription state, filters, annotations, deliveries, and private credentials remain tenant-scoped. Private feeds should not be deduplicated across tenants unless the authorization model explicitly permits it. Encryption keys and secret access should follow the same isolation rules as the records they protect.</p>

<h2>Separate the control plane and data plane</h2>
<p>The control plane handles source registration, credentials, subscription rules, destination configuration, quotas, and audit logs. The data plane fetches sources, parses documents, stores entries, and delivers events. A slow control-plane request should not block ingestion, and a burst of feed updates should not prevent customers from changing configuration. Communicate between the planes through durable commands and events.</p>
<p>Configuration changes need versions. A delivery event should record which subscription and filter version matched it. If a customer changes a rule, new items follow the new version while historical deliveries remain explainable. For destructive changes, such as removing a source or destination, mark the resource inactive before asynchronous cleanup so in-flight jobs can resolve state consistently.</p>

<h2>Design list and history APIs for change</h2>
<p>Use cursor pagination with stable ordering for entries, observations, and deliveries. Offset pagination is unreliable when new records arrive continuously. State the sort key and whether results include updates to existing entries. Offer filters for source, publication range, first-seen range, tag, delivery state, and content type, but avoid arbitrary query languages until common access patterns are understood.</p>
<p>Feed history can outgrow a single current document. RFC 5005 describes <a href="https://www.rfc-editor.org/info/rfc5005/" target="_blank" rel="noopener noreferrer">feed paging and archiving</a> concepts for navigating complete and paged feeds. Even when your API uses JSON cursors, the underlying distinction is valuable: a current feed is a view of recent items, while an archive is a stable historical collection. Document retention limits so clients know whether the API is a synchronization log or a recent-content window.</p>

<h2>Offer idempotent writes and webhooks</h2>
<p>Source registration, subscription creation, and replay requests should accept idempotency keys. A customer retry after a timeout should receive the original result rather than create duplicates. Store the key with a request fingerprint and reject reuse with different parameters. For update operations, use resource versions or conditional writes to prevent one administrator from overwriting another's changes.</p>
<p>Outbound webhooks need stable event IDs, signatures, timestamps, retry policy, and a replay endpoint. Sign the raw body with a per-destination secret and include a narrow tolerance for timestamp validation. Do not change an event payload during retries. Record every attempt and response summary. A customer should be able to verify delivery, rotate a secret, disable a destination, and replay a bounded range without support intervention.</p>

<h2>Make freshness and service levels precise</h2>
<p>“Real time” is not a useful promise for polled public feeds. Define measurable stages: time from scheduled fetch to attempt, time from successful observation to committed entry, and time from committed event to accepted delivery. Report the last successful source observation and whether the current API response comes from a fresh or stale snapshot. Separate platform delay from publisher cadence and destination availability.</p>
<p>Create service tiers around controllable resources such as minimum polling interval, source count, retention, webhook concurrency, and support response. Do not imply that a premium tier can force a third-party publisher to update faster. For high-value sources, push subscriptions or publisher partnerships can reduce delay, but reconciliation polling should remain part of the reliability model.</p>

<h2>Build quotas around cost drivers</h2>
<p>API requests are only one cost. Fetch frequency, document size, parsing complexity, retained history, filter fan-out, webhook attempts, and egress all contribute. Set quotas on sources, active subscriptions, refresh requests, history retention, and delivery throughput. Expose usage with enough detail for customers to adjust behavior before hitting a limit.</p>
<p>Apply backpressure rather than allowing one tenant to exhaust shared workers. Priority queues can protect scheduled ingestion from manual refresh bursts. Per-tenant and per-destination concurrency limits keep retries contained. When a quota is reached, return a stable error with the relevant limit and reset behavior. Silent dropping is unacceptable for a syndication product.</p>

<h2>Provide governance and audit trails</h2>
<p>Record who registered a source, changed credentials, edited filters, rotated a webhook secret, requested a replay, or deleted data. Audit records should include actor, action, resource, time, request ID, and a safe summary of the change. Protect the audit log from ordinary mutation and define retention independently from content retention.</p>
<p>Source governance also matters. Track ownership claims, allowed domains, robots or publisher policies where relevant, and complaints. Provide a process to pause or remove a source without deleting evidence needed to investigate. For customer-provided credentials, show last use and support immediate revocation. The <a href="https://devrss.com/about/">DevRSS platform principles</a> emphasize portability and responsible fetching as product requirements, not marketing extras.</p>

<h2>Expose analytics that answer operational questions</h2>
<p>Customers usually need source health, entries discovered, delivery success, latency, and filter match counts. Publishers may need aggregate request patterns and subscriber counts where contracts permit. Operators need cost, queue depth, parser warnings, host throttling, and tenant concentration. Build analytics from event records with clear definitions rather than ambiguous counters.</p>
<p>Avoid implying audience engagement from feed fetches alone. An aggregator request does not equal a human read. Label metrics precisely: fetches, unique subscriber configurations, delivered events, accepted webhooks, or clicked canonical links when measured with consent. Keep analytics queries isolated from the transaction path so dashboards cannot slow ingestion.</p>

<h2>Commercial platform checklist</h2>
<ul>
  <li>Model sources, observations, entries, subscriptions, destinations, and deliveries as distinct resources.</li>
  <li>Enforce tenant scope in storage, queues, caches, secrets, and tests.</li>
  <li>Version configuration and record the version used for every matched event.</li>
  <li>Use cursor pagination and state retention and archive semantics clearly.</li>
  <li>Support idempotent control-plane writes and signed, replayable webhook events.</li>
  <li>Define freshness and service levels only for stages the platform controls.</li>
  <li>Quota fetch, storage, fan-out, and delivery cost—not only API calls.</li>
  <li>Provide audit trails, source governance, usage visibility, and safe offboarding.</li>
</ul>

<h2>Sell a dependable boundary</h2>
<p>The commercial value of a B2B feed API is predictable behavior across messy external sources. Customers should not need to understand parser quirks, polling schedules, or retry mechanics to receive a stable stream. They do need transparent identifiers, freshness, errors, limits, and audit evidence.</p>
<p>Keep shared ingestion efficient and tenant behavior isolated. Keep the control plane explicit and the data plane replayable. With those boundaries, a platform can add custom feeds, analytics, webhooks, and archival products without compromising the core promise: an update published elsewhere becomes a trustworthy record here.</p>
]]></content:encoded>
</item>
<item>
<title>Feed Polling at Scale: ETags, Backoff, and Deduplication</title>
<link>https://devrss.com/blog/feed-polling-etag-deduplication/</link>
<guid isPermaLink="true">https://devrss.com/blog/feed-polling-etag-deduplication/</guid>
<pubDate>Thu, 16 Jul 2026 12:00:00 GMT</pubDate>
<category>Feed Operations</category>
<description><![CDATA[Scale feed polling with conditional requests, adaptive schedules, host fairness, deterministic identity, and replay-safe processing.]]></description>
<content:encoded><![CDATA[
<p>Polling thousands of feeds is not primarily a parsing challenge. It is a scheduling and fairness problem constrained by remote servers you do not control. A naive worker that fetches every source every five minutes wastes bandwidth on dormant feeds, overloads shared hosts at the top of the minute, and repeats work when documents have not changed. A scalable poller learns source behavior, uses HTTP validators, and commits results idempotently.</p>
<p>The goal is not the shortest theoretical delay. It is dependable discovery with respectful network behavior and predictable cost. High-value active feeds can be checked frequently, quiet feeds can back off, and every source can receive eventual attention. When push notifications are available, polling remains the reconciliation channel that repairs missed events.</p>

<h2>Store scheduling state per source</h2>
<p>Each source needs a next-attempt time, last-attempt time, last-success time, last-change time, consecutive failure count, recent change intervals, ETag, Last-Modified value, and host key. Keep this state in durable storage so a worker restart does not reset every source to “fetch now.” A scheduler should claim due sources with a lease, allowing another worker to recover work if the first worker fails.</p>
<p>Separate source state from tenant subscriptions. Several customers may follow the same public feed, but the network fetch should usually be shared. Normalize one source snapshot, then apply tenant-specific filters and delivery rules downstream. This reduces load and prevents one popular feed from being fetched once per customer. Authorization still applies to private feeds, where credentials or network boundaries make sharing inappropriate.</p>

<h2>Use conditional requests correctly</h2>
<p>When a successful response provides an ETag or Last-Modified value, store it and send the corresponding conditional header on the next request. The server can respond that the representation has not changed, avoiding a full body transfer. The semantics of validators and conditions are defined in the <a href="https://www.rfc-editor.org/rfc/rfc9110#name-conditional-requests" target="_blank" rel="noopener noreferrer">HTTP conditional requests section of RFC 9110</a>. Preserve the exact validator value rather than trying to interpret its internal format.</p>
<p>A not-modified response is a successful fetch. Update last-attempt and last-success, record latency, and schedule the next check without parsing. If the server stops sending validators, continue safely with full responses and compare a body hash. Do not send both validators blindly if your client library's precedence is unclear; test the resulting headers and document your policy. Redirects may lead to a resource with different validators, so bind validators to the resolved representation URL.</p>

<h2>Adapt intervals to observed change</h2>
<p>Begin with a conservative interval and adjust after collecting history. If a feed changes frequently, shorten the interval within configured limits. If it remains unchanged for many checks, lengthen the interval. Use a moving window or exponential smoothing so one burst does not permanently classify a source as active. Apply a minimum interval to protect publishers and a maximum interval that still meets your product's freshness promise.</p>
<p>Publisher hints can inform scheduling but should not override policy automatically. Cache headers, RSS scheduling elements, or documented update windows may be useful. Compare hints with observed behavior and cap them. A malicious or mistaken source should not be able to demand one request per second or delay checks for a year. The <a href="https://devrss.com/monitoring/">monitoring layer</a> can expose both the configured interval and the reason it was chosen.</p>

<h2>Add jitter and host-level fairness</h2>
<p>Never align every source to round clock boundaries. Add random jitter when scheduling the next attempt so work spreads across time. Jitter should be stable enough to avoid chaotic rescheduling but large enough to break synchronized waves after a restart. Rebuild the due queue gradually during recovery instead of releasing the entire backlog at once.</p>
<p>Apply concurrency and request-rate limits per host or registrable domain. Thousands of feed URLs may be served by one publishing platform, CDN origin, or organization. A global worker limit does not prevent one host from receiving a burst. Use host queues, token buckets, or weighted fair scheduling so active domains cannot starve smaller ones. Respect explicit retry guidance and slow down hosts that return throttling or overload responses.</p>

<h2>Handle failures with classified backoff</h2>
<p>Not every failure deserves the same retry. A timeout or server error is usually transient and can use exponential backoff with jitter. A not-found response may indicate a permanent move or deleted feed and should be retried less often after checking redirects and discovery. An authorization failure should stop automated retries until credentials change. A parse failure can be retried on the normal cadence because the publisher may fix the document, but repeated identical invalid bodies do not need expensive reprocessing.</p>
<p>Keep a ceiling on backoff so recovery is eventually detected. Reset the failure counter only after a meaningful success. A not-modified response counts as success; a network response that cannot be parsed does not. Group failures by host and signature to identify infrastructure incidents. Circuit breakers can pause aggressive attempts to a failing host while allowing occasional probes.</p>

<h2>Deduplicate at more than one layer</h2>
<p>Network deduplication avoids parsing unchanged documents through validators or body hashes. Document deduplication records whether the normalized feed snapshot changed. Entry deduplication decides whether an item is new, updated, or already known. Delivery deduplication prevents the same item event from reaching a destination twice. Each layer needs its own key because one key cannot answer all four questions.</p>
<p>For entries, prefer a stable source-provided ID. Fall back to a canonical URL, then a documented fingerprint based on fields that are likely stable. Do not include the current fetch time. Store identity aliases when a publisher changes from one identifier style to another, and surface collisions for review. Compare a content fingerprint separately so an existing entry can be updated without being recreated.</p>

<h2>Commit before fan-out</h2>
<p>Parsing workers should write the source observation, snapshot, entries, and change events in a durable transaction or an equivalent atomic workflow. Only then should they enqueue downstream deliveries. If the worker crashes after commit but before enqueue, an outbox process can recover the pending events. If it crashes before commit, the source can be fetched again without creating inconsistent partial state.</p>
<p>Every delivery event should have a stable idempotency key. A destination retry then reuses the same key, and a consumer can safely reject duplicates. Keep source polling independent from slow destinations; otherwise one failing webhook can delay discovery for every subscriber. The <a href="https://devrss.com/syndication/">delivery architecture</a> should consume committed events from a queue and maintain its own retry schedule.</p>

<h2>Operate the scheduler with measurable goals</h2>
<p>Track due-source lag, fetch throughput, success rate, not-modified rate, bytes saved by conditional requests, parser time, new items per fetch, host throttling, and queue depth. Due-source lag—the difference between scheduled and actual start time—is a direct measure of whether the system is keeping up. Break it down by priority and host so a healthy global average does not hide a starved queue.</p>
<p>Set capacity plans using observed latency and change rates, not only source count. Ten thousand fast cached feeds can be cheaper than one thousand slow feeds with large bodies. Maintain a controlled load test with synthetic hosts that produce redirects, delays, compression, invalid documents, and changing validators. Test restart behavior and backlog recovery, because those moments create the largest accidental bursts.</p>

<h2>Scaling checklist</h2>
<ul>
  <li>Persist per-source schedule, validator, health, and observation state.</li>
  <li>Share public-source fetches across tenant subscriptions where policy allows.</li>
  <li>Use ETag and Last-Modified validators and treat not-modified as success.</li>
  <li>Adapt intervals from change history within strict minimum and maximum bounds.</li>
  <li>Add jitter, per-host concurrency limits, and fair scheduling.</li>
  <li>Classify failures and apply policy-specific backoff with eventual probes.</li>
  <li>Deduplicate documents, entries, change events, and deliveries independently.</li>
  <li>Commit normalized state before fan-out and monitor due-source lag.</li>
</ul>

<h2>Scale by doing less unnecessary work</h2>
<p>A scalable poller is not the one with the largest worker fleet. It is the one that avoids downloading unchanged bytes, avoids parsing identical documents, avoids fetching a shared source repeatedly, and avoids notifying consumers twice. HTTP already provides useful mechanisms; the system's job is to apply them consistently and measure the result.</p>
<p>Build fairness and recovery into the scheduler from the beginning. When the queue grows or a host fails, the poller should degrade gradually, preserve important sources, and recover without a burst. That operational behavior matters more than shaving a few seconds from the happy path.</p>
]]></content:encoded>
</item>
<item>
<title>Podcast RSS for Developer Shows: Metadata, Enclosures, and Delivery</title>
<link>https://devrss.com/blog/podcast-rss-developer-shows/</link>
<guid isPermaLink="true">https://devrss.com/blog/podcast-rss-developer-shows/</guid>
<pubDate>Tue, 07 Apr 2026 12:00:00 GMT</pubDate>
<category>Guides</category>
<description><![CDATA[Publish a durable podcast feed for technical shows with stable GUIDs, streamable enclosures, accurate metadata, and migration-safe URLs.]]></description>
<content:encoded><![CDATA[
<p>A developer podcast may begin as an audio file and a blog post, but distribution depends on a long-lived RSS contract. Podcast applications use the feed to discover the show, identify episodes, retrieve artwork, stream media, and detect updates. A feed that looks correct in a browser can still fail because an enclosure cannot handle range requests, an episode GUID changes, or the artwork URL is private.</p>
<p>The durable approach is to treat the podcast feed as a published API. Generate it from canonical episode records, keep identifiers and URLs stable, validate every build, and monitor media delivery separately from XML delivery. This matters even for a small show: subscribers may keep the feed URL for years, and directory caches make migrations slower than ordinary web-page changes.</p>

<h2>Model the show and episodes separately</h2>
<p>The show record contains the title, description, canonical site, language, author or organization, categories, artwork, ownership information, and feed URL. Episode records contain stable IDs, titles, summaries, publication dates, media metadata, duration, episode type, and canonical pages. Generate both the website and feed from these records so a correction does not require editing XML by hand.</p>
<p>Define which fields permit rich HTML and which are plain text. Keep a concise summary for clients with limited displays and a full description for show notes. Technical shows often include code, links, and timestamps; ensure the essential meaning survives when styles, tables, or advanced markup are removed. Host complete notes on a canonical episode page and use the feed to provide a readable portable version.</p>

<h2>Make episode GUIDs permanent</h2>
<p>An episode GUID identifies the logical episode across title edits, file moves, and metadata corrections. Generate it once and store it. Do not derive it from a mutable audio URL, title, publication date string, or build timestamp. A URL can be used as a GUID only when you control it and guarantee that it will remain stable. Otherwise use a namespaced opaque identifier or a stable canonical episode URL.</p>
<p>Changing a GUID can cause podcast applications to treat an old episode as new, producing duplicate downloads and notifications. Build a test that compares GUIDs between releases and requires an explicit override for any change. If an episode is replaced with a corrected audio file, keep the GUID and update the enclosure according to your correction policy. If it is a genuinely new edition, publish it as a new episode with a new identity.</p>

<h2>Serve enclosures as streamable media</h2>
<p>Each episode enclosure needs a public URL, correct media type, and accurate size where required. The media server must handle reliable downloads and the request patterns used by podcast applications. Apple documents its expectations in the <a href="https://podcasters.apple.com/support/823-podcast-requirements" target="_blank" rel="noopener noreferrer">podcast RSS feed requirements</a>, including unique enclosure URLs and required enclosure attributes. Test the URL without cookies, authentication, or a browser session.</p>
<p>Support HEAD requests and byte-range requests so clients can inspect and seek within media. Return a consistent content length and media type. Avoid short-lived signed URLs in the feed; they may expire after a directory caches them. Put a stable CDN URL in the enclosure and handle origin changes behind that URL. Monitor media from multiple regions because a healthy XML feed does not prove that the audio file is reachable.</p>

<h2>Use accurate publication dates and episode order</h2>
<p>Publication dates affect sorting and notification. Store a timezone-aware instant and emit the required date syntax. Do not rewrite old episode dates during a feed rebuild. If an episode is scheduled, publish it only when the release time arrives unless the target platform has a documented scheduling model. A future date in a publicly visible item can behave inconsistently across clients.</p>
<p>Order items newest first, but do not depend on array position as identity. Keep season and episode numbers as optional metadata where they make sense. Developer shows may publish bonus briefings, conference dispatches, or trailers; label episode type explicitly rather than forcing every item into a numbered sequence. Maintain chronological integrity even when titles are edited.</p>

<h2>Design artwork and metadata for constrained clients</h2>
<p>Show artwork should be square, readable at small sizes, and served from a stable HTTPS URL. Avoid critical text near the edges because applications crop and mask images differently. Episode-specific artwork can add context, but the show image remains the fallback. Update image URLs when the bytes change significantly so caches can refresh, while keeping the general path strategy predictable.</p>
<p>Descriptions should lead with the value of the episode, not a wall of sponsor or boilerplate text. Include guests, topics, and relevant links in a consistent order. Use categories that reflect the show rather than every topic mentioned in one episode. Keep ownership and contact metadata current; directories may use it for verification even when it is not displayed publicly. The <a href="https://devrss.com/contact/">DevRSS contact model</a> illustrates keeping a visible, stable channel without embedding a form.</p>

<h2>Validate feed and media together</h2>
<p>Run the XML through an independent feed validator and the target podcast directory's validation flow. Then test each enclosure with HEAD, a small byte range, and a complete download. Confirm that the reported size matches the file and that redirects are limited and stable. Check the show and recent episodes in multiple podcast applications because rendering and cache behavior vary.</p>
<p>Automate checks in the publishing pipeline. Reject duplicate GUIDs, duplicate enclosure URLs, missing media types, invalid dates, inaccessible artwork, and private enclosure hosts. Warn when a description exceeds a practical limit or when an episode lacks a canonical page. Save a generated-feed fixture for each release so a later incident can be compared with what was actually published.</p>

<h2>Plan migrations before changing the feed URL</h2>
<p>The feed URL is the subscription anchor. If hosting must move, keep the old URL under your control and serve a permanent redirect to the new feed. Update any feed-level migration metadata required by the target ecosystem, change website autodiscovery, and leave the redirect active for a long period. Test the migration with a nonproduction feed before moving the primary show.</p>
<p>Media URLs can be migrated behind a CDN or stable redirect, but avoid changing all enclosure URLs and the feed URL simultaneously. Stage changes so failures can be isolated. Keep old media available until download traffic confirms that caches and clients have moved. A domain owned by the publisher provides more long-term control than a host-specific feed URL.</p>

<h2>Use analytics without damaging reliability</h2>
<p>Podcast download analytics often rely on media requests, but redirect chains and dynamic tracking can reduce compatibility. Keep measurement infrastructure transparent to clients: stable URLs, fast responses, correct range behavior, and privacy-aware logs. Do not require query parameters that change per request or cookies that a podcast application will not send.</p>
<p>Count downloads according to a documented methodology and avoid presenting raw requests as listeners. Bots, retries, partial ranges, and automated prefetching complicate interpretation. The feed itself can be monitored for fetch patterns, but do not embed user-specific identifiers in a public RSS document. Reliability and subscriber privacy should take priority over granular attribution.</p>

<h2>Publishing checklist</h2>
<ul>
  <li>Generate show and episode metadata from canonical records rather than hand-edited XML.</li>
  <li>Assign permanent GUIDs and test that rebuilds never regenerate them.</li>
  <li>Use unique, stable, public enclosure URLs with correct type, size, HEAD, and byte-range support.</li>
  <li>Preserve publication dates and label episode types, seasons, and numbers only when meaningful.</li>
  <li>Serve readable square artwork from stable HTTPS URLs and test small-size rendering.</li>
  <li>Validate the feed, artwork, and every recent media enclosure before publication.</li>
  <li>Own the feed domain where possible and rehearse redirect-based migrations.</li>
  <li>Monitor XML availability, media delivery, directory ingestion, and unexpected identifier changes separately.</li>
</ul>

<h2>A podcast feed is a long-term promise</h2>
<p>Podcast RSS succeeds when a subscriber can disappear for months, reopen an application, and continue receiving the same show without duplicates or broken files. That outcome depends on stable identity, durable URLs, correct media delivery, and conservative migrations more than elaborate markup.</p>
<p>Treat every episode as a record with a permanent ID and every enclosure as public infrastructure. With automated validation and monitoring, a developer show can remain portable across directories and hosting providers while preserving the audience's subscriptions.</p>
]]></content:encoded>
</item>
<item>
<title>How to Validate and Debug RSS and Atom Feeds in Production</title>
<link>https://devrss.com/blog/validate-debug-rss-atom/</link>
<guid isPermaLink="true">https://devrss.com/blog/validate-debug-rss-atom/</guid>
<pubDate>Tue, 20 Jan 2026 12:00:00 GMT</pubDate>
<category>Feed Operations</category>
<description><![CDATA[A repeatable debugging workflow for malformed XML, namespaces, dates, identifiers, redirects, content encoding, and validator warnings.]]></description>
<content:encoded><![CDATA[
<p>Feed failures often arrive as vague reports: a reader stopped updating, a podcast directory rejected a URL, or a webhook pipeline began producing duplicate entries. The fastest route to a fix is a layered debugging process that separates transport, document syntax, specification semantics, and consumer behavior. Jumping directly to changing XML tags can hide the real problem, especially when a redirect, cache, or incorrect content type sits in front of the document.</p>
<p>Validation is valuable, but a validator is one instrument. A feed can pass syntax checks and still have unstable identifiers, misleading dates, or links that break after a deployment. It can also contain a minor warning that tolerant readers handle safely. Production debugging therefore combines an independent validator, raw HTTP inspection, format-aware parsing, and tests based on your actual consumers.</p>

<h2>Capture the exact failing response</h2>
<p>Start from the URL the consumer uses, not a file copied from a build directory. Record the request time, DNS result, redirect chain, final URL, status, response headers, body bytes, and content encoding. Use a tool that does not silently follow redirects or reformat XML until you have inspected each hop. Many “invalid feed” incidents are actually login pages, CDN challenges, expired redirects, or HTML error documents served with a successful status.</p>
<p>Repeat the request from a network location similar to the affected consumer. A feed can work from an office while failing from a cloud region because of firewall policy or bot protection. Preserve a bounded raw sample with secrets removed. Once the exact response is captured, later parser experiments can be replayed without repeatedly hitting the publisher or losing the transient failure.</p>

<h2>Run an independent validator</h2>
<p>The <a href="https://validator.w3.org/feed/" target="_blank" rel="noopener noreferrer">W3C Feed Validation Service</a> checks Atom and RSS syntax and reports errors and warnings with context. Use it on a publicly reachable test URL or validate the captured content directly. Read the detailed explanation for each message instead of treating the result as a binary badge. Fix fatal structural errors first, then evaluate warnings based on their effect on interoperability.</p>
<p>Keep validation in continuous integration for feeds you publish. Generate the production artifact, run it through a validator or local validator library, and parse it with at least one independent consumer. A template that produces well-formed XML can still create invalid output when a title contains an unescaped character or an optional field is emitted in the wrong place. Test representative content, not only an empty sample.</p>

<h2>Check XML well-formedness before feed semantics</h2>
<p>XML must be well formed before an RSS or Atom parser can interpret it. Common failures include unescaped ampersands, mismatched tags, duplicate attributes, invalid control characters, and incorrect character encoding declarations. CDATA sections do not excuse every character, and blindly wrapping content in CDATA can fail when the content includes the closing delimiter. Use an XML serializer rather than string concatenation.</p>
<p>Confirm that the bytes match the declared encoding and the HTTP charset. UTF-8 is the simplest default. A document declared as UTF-8 but encoded differently may work in one browser and fail in a strict parser. Remove invisible control characters introduced by copy-and-paste or source databases. When content contains HTML, encode it as text according to the feed format rather than nesting arbitrary markup in a field that does not permit it.</p>

<h2>Verify namespaces and element placement</h2>
<p>RSS extensions rely on XML namespaces. Every prefixed element must have a namespace declaration in scope, and the namespace URI must match the extension specification. Do not copy only the prefixed tag from an example. Atom uses a default namespace for its core elements; omitting it can turn a visually familiar document into unrelated XML that an Atom parser ignores.</p>
<p>Element order and cardinality can matter. An extension may be valid only at channel level, item level, feed level, or entry level. Some elements may occur once, while others may repeat. If a validator reports an unexpected element, inspect the parent and namespace before renaming anything. Preserve a minimal failing fixture, then add fields back one at a time to identify the exact structural conflict.</p>

<h2>Test identifiers as long-lived data</h2>
<p>A syntactically valid feed can still create duplicates if item identifiers change on every build. RSS GUIDs and Atom IDs should represent the logical entry, not the current render time. Do not use a random value, a build hash, or an automatically regenerated URL when the content is the same entry. If a canonical URL changes, keep the identifier stable and update the link unless the publisher's contract explicitly defines a new item.</p>
<p>Build a regression test that generates the feed twice from unchanged records and compares identifiers byte for byte. Then edit a title or body and confirm that the identifier remains stable while the update date changes appropriately. Add a new entry and confirm that older IDs remain untouched. Consumer behavior is much more reliable when identity is tested as an invariant rather than left to templates.</p>

<h2>Separate publication and modification dates</h2>
<p>Dates cause failures through invalid syntax, missing time zones, impossible values, and semantic misuse. Use the date format required by the chosen feed type and include a time-zone offset or UTC marker. Do not set every entry's publication date to the build time. The publication date should reflect when the entry was first released, while a modification field can reflect a substantive later change.</p>
<p>Feed-level update dates should describe the feed's content, not a cron job that rewrote identical bytes. If the document changes only because a generator inserts the current time, conditional caching becomes ineffective and consumers may repeatedly process the feed. Derive the feed update time from the newest relevant entry change and keep the output deterministic.</p>

<h2>Inspect links, redirects, and self references</h2>
<p>Check every canonical item link, enclosure URL, icon, and feed-level link. Resolve relative URLs as a consumer would. Verify that the feed URL returns the feed directly or through a deliberate permanent redirect, and that the final document points to the correct home page. Atom self links should identify the feed representation rather than the site home page. RSS deployments often add an Atom namespace self link for better discovery; ensure it matches the canonical feed URL.</p>
<p>When migrating a feed, keep the old URL responding with a permanent redirect for an extended period and update discovery links. Test whether clients preserve subscriptions through the move. Do not create redirect chains across several historical domains. Monitoring should record the chain so a newly introduced temporary redirect or authentication page is visible.</p>

<h2>Debug content and enclosure behavior</h2>
<p>Decide whether a field contains plain text, escaped HTML, or full content, and emit it consistently. Sanitize user-generated HTML before syndication, use absolute URLs for embedded resources, and avoid scripts or form controls. Some consumers strip styles and advanced markup, so the entry should remain understandable as simple HTML. Compare the raw field with how several target readers render it.</p>
<p>For media enclosures, verify URL, media type, length when required, range support, and public accessibility. A browser downloading the file successfully does not prove that a podcast or feed client can stream it. Test HEAD and range requests where the target ecosystem expects them. Keep enclosure URLs unique and stable; replacing the bytes behind an old URL can confuse caches and clients.</p>

<h2>Reproduce with a minimal feed</h2>
<p>When the cause remains unclear, create the smallest document that preserves the failure. Start with the required feed and one entry. Remove extensions, optional metadata, rich content, and additional items. Validate and test after each removal. Once the failure disappears, add the last piece back. This technique identifies whether the issue is document-wide, entry-specific, or caused by an extension interaction.</p>
<p>Store minimal fixtures in the parser test suite. Include the original failure, the corrected version, and the expected normalized output. Over time, the fixture library becomes a practical compatibility specification for your platform. The <a href="https://devrss.com/monitoring/">feed monitoring pipeline</a> can also replay captured documents against a candidate parser before deployment.</p>

<h2>Production validation checklist</h2>
<ul>
  <li>Inspect the complete HTTP transaction and save the exact response bytes.</li>
  <li>Validate independently, fixing structural errors before evaluating warnings.</li>
  <li>Serialize XML with a library and align byte encoding, declaration, and HTTP headers.</li>
  <li>Verify namespace declarations, parent elements, order, and cardinality.</li>
  <li>Test stable entry IDs across rebuilds and content edits.</li>
  <li>Use correct date syntax and preserve publication versus modification semantics.</li>
  <li>Check canonical links, self links, redirects, enclosures, and absolute embedded URLs.</li>
  <li>Add every real incident as a minimal regression fixture.</li>
</ul>

<h2>Make validation part of publishing</h2>
<p>The best feed incident is the one rejected before deployment. Generate feeds deterministically, validate them in CI, parse them with independent tooling, and compare identity and date invariants. In production, monitor the actual URL and preserve evidence from failures.</p>
<p>When a problem reaches users, debug from the network inward: transport, bytes, XML, feed semantics, identity, then consumer behavior. That order prevents speculative edits and produces fixes that remain correct across readers rather than patches for one symptom.</p>
]]></content:encoded>
</item>
<item>
<title>Turn GitHub Releases and CI Events into Clean Team Feeds</title>
<link>https://devrss.com/blog/github-releases-ci-feeds/</link>
<guid isPermaLink="true">https://devrss.com/blog/github-releases-ci-feeds/</guid>
<pubDate>Mon, 22 Sep 2025 12:00:00 GMT</pubDate>
<category>APIs &amp; Automation</category>
<description><![CDATA[Create a stable release-intelligence stream from repository releases, tags, build results, and deployment events without flooding engineering teams.]]></description>
<content:encoded><![CDATA[
<p>Repository activity is abundant, but most of it is not release intelligence. Commits, pull requests, tags, workflow runs, deployments, and published releases each describe a different point in the software delivery process. Sending all of them to a team channel creates noise. A clean release feed selects durable milestones, enriches them with context, and preserves enough identity to support deduplication and audit.</p>
<p>The design begins by separating facts from notifications. A Git tag is a reference. A release object adds a title, notes, assets, draft state, and publication state. A CI run proves that a workflow executed, while a deployment event may indicate that an artifact reached an environment. A useful feed composes these facts into a small number of normalized entries rather than mirroring every provider event.</p>

<h2>Define what counts as a release event</h2>
<p>Write a policy for each repository class. A library may publish an entry when a non-draft release becomes public. An internal service may publish when a version is deployed to production. A mobile application may publish after a store submission reaches an approved state. Avoid using “new tag” as a universal rule because teams create tags for testing, automation, or rollback points that are not intended for consumers.</p>
<p>Represent the policy as code or configuration with an explicit version. Store which policy produced each feed entry. When the policy changes, do not silently reinterpret history. New events can follow the new rule, while existing entries retain their provenance. This makes a release stream explainable during incident review and prevents a configuration edit from republishing old activity.</p>

<h2>Use provider APIs for authoritative metadata</h2>
<p>Webhooks are excellent triggers, but the event payload may not include every field needed for a durable entry. After accepting a provider event, fetch the authoritative object using a constrained API client. For GitHub releases, the <a href="https://docs.github.com/en/rest/releases/releases" target="_blank" rel="noopener noreferrer">GitHub Releases REST API documentation</a> defines endpoints and release fields. Capture the immutable provider ID, repository identity, tag name, target commit, draft and prerelease flags, publication time, author, notes, and assets.</p>
<p>Do not make the webhook response wait for API enrichment. Authenticate the webhook, store its provider delivery ID and raw body hash, enqueue a job, and acknowledge quickly. The worker can then retrieve the release, apply policy, and create or update a normalized entry. If the API is temporarily unavailable, retries occur without asking the provider to resend the original webhook.</p>

<h2>Normalize repository and version identity</h2>
<p>Repository names can change, transfer between organizations, or be mirrored. Use the provider's immutable repository ID as the primary external key and store the current owner/name as display metadata. A release key can combine provider, repository ID, and release ID. Keep the tag name and semantic version as separate fields because not every tag is a valid semantic version and not every product uses the same prefix convention.</p>
<p>Parse semantic versions only when the repository policy says they are meaningful. Record the original tag exactly. A normalized version object can contain major, minor, patch, prerelease, and build metadata when parsing succeeds, plus a boolean indicating compliance. This supports filters such as “stable minor releases” without rejecting date-based or product-specific version labels.</p>

<h2>Combine CI evidence without creating duplicates</h2>
<p>A release may trigger several workflows across operating systems, architectures, and packaging targets. Those runs are evidence attached to one release, not separate feed entries. Model a release entry with a verification summary: required checks, latest conclusion, artifact provenance, and completion time. Update the existing entry when verification changes, and emit a change event only if subscribers opted into status transitions.</p>
<p>Use stable workflow and run identifiers, not display names alone. Jobs can be rerun, and a later attempt may replace a failure. Store attempts and select the policy-relevant result. If a release is published before all required checks complete, mark verification as pending. Consumers can decide whether to notify immediately, wait for success, or send a follow-up. This is more honest than treating publication and validation as one atomic event.</p>

<h2>Represent deployments as environment-specific facts</h2>
<p>A deployment is not synonymous with a release. The same version may move through test, staging, canary, and production, and production may have several regions. Normalize deployment events with environment ID, environment class, artifact or commit identity, start and completion times, result, and provider event ID. Then apply a publication rule such as “create one feed update when the first production region succeeds” or “update the entry when global rollout completes.”</p>
<p>Keep sensitive environment details out of public feeds. Internal feeds can include deployment links and runbook references under authorization, while public feeds should expose only customer-relevant status. The <a href="https://devrss.com/feeds/">custom developer feed model</a> can produce different views from the same normalized event history without duplicating ingestion.</p>

<h2>Generate useful release content</h2>
<p>A clean entry should answer what changed, who is affected, where to learn more, and what action is required. Preserve the publisher's release notes, but sanitize HTML and apply length limits for feed summaries. Extract structured highlights only when the source follows a documented convention. Do not invent breaking-change labels from keyword guesses. Instead, let maintainers supply categories in release metadata or a checked-in changelog format.</p>
<p>Link to the canonical release page, documentation, migration guide, and downloadable assets when available. Enclosures can represent artifacts in a feed, but avoid listing every asset if the result becomes unwieldy. A concise entry can include the primary package or installer and leave the complete asset list on the release page. Retain checksums and signatures in the API model even if the human-facing feed only links to them.</p>

<h2>Handle edits, drafts, prereleases, and deletions</h2>
<p>Release objects change. Notes are corrected, assets are replaced, prereleases become stable, and releases can be deleted. Store the latest provider update timestamp and a content fingerprint. When an object changes, update the normalized record and emit an update event with the fields that changed. Keep the stable entry ID so feed readers do not see an edit as a new release.</p>
<p>Draft releases should remain private to authorized workflows. Prereleases should be included only for subscribers who request them. If a public release is deleted, retain a tombstone internally and decide how the feed communicates withdrawal. Removing the item silently can leave consumers with an artifact they believe is current. A withdrawal entry or status update is often clearer, especially for security or compliance-sensitive software.</p>

<h2>Control fan-out and notification noise</h2>
<p>Let teams subscribe by organization, repository, package, environment, stability channel, version range, or topic. Apply filters before delivery and record the matched rule. Batch related events within a short window when a coordinated release touches many repositories. A digest can group them under a product version while preserving links to each component release.</p>
<p>Use destination-specific formatting. A feed reader benefits from complete notes and stable identifiers. A chat notification needs a concise headline and a few highlights. A webhook consumer needs structured fields and an idempotency key. Generate each representation from the same normalized record so they do not disagree. The <a href="https://devrss.com/syndication/">syndication layer</a> should own formatting and retries rather than embedding destination logic in CI workflows.</p>

<h2>Implementation checklist</h2>
<ul>
  <li>Define release policies separately for libraries, services, applications, and internal environments.</li>
  <li>Verify provider webhook signatures and deduplicate by provider delivery ID.</li>
  <li>Enrich events asynchronously from authoritative APIs with least-privilege credentials.</li>
  <li>Key repositories and releases by immutable provider IDs, while retaining display names and tags.</li>
  <li>Attach CI and deployment evidence to release records instead of creating uncontrolled event streams.</li>
  <li>Preserve draft, prerelease, published, withdrawn, and verification states explicitly.</li>
  <li>Sanitize release content and keep public and private feed views separate.</li>
  <li>Offer filters, batching, idempotent delivery, and replay from normalized history.</li>
</ul>

<h2>Publish milestones, not exhaust</h2>
<p>A release feed should help a developer understand software movement without watching the machinery produce it. Provider webhooks and APIs supply detailed facts; your policy turns those facts into durable milestones. Stable identity, asynchronous enrichment, explicit state, and destination-specific delivery are the core design tools.</p>
<p>When the feed is trustworthy, teams can use it for dependency awareness, platform announcements, customer changelogs, and operational coordination. The result is not another firehose. It is a curated record of what shipped and why it matters.</p>
]]></content:encoded>
</item>
<item>
<title>Engineering Blog and Changelog Feed Monitoring That Catches Real Releases</title>
<link>https://devrss.com/blog/engineering-blog-feed-monitoring/</link>
<guid isPermaLink="true">https://devrss.com/blog/engineering-blog-feed-monitoring/</guid>
<pubDate>Wed, 14 May 2025 12:00:00 GMT</pubDate>
<category>Feed Operations</category>
<description><![CDATA[Build monitoring that distinguishes source outages, stale feeds, parser regressions, duplicate entries, and downstream delivery failures.]]></description>
<content:encoded><![CDATA[
<p>A feed monitor that only checks for an HTTP 200 response will miss the failures that matter. A source can be reachable while serving an HTML error page, a valid feed can stop receiving new entries, a parser upgrade can discard identifiers, and a webhook destination can fail after ingestion succeeds. Useful monitoring follows an update through the entire pipeline and reports which stage is unhealthy.</p>
<p>Engineering blogs and changelogs make this especially important because the cadence is irregular. A quiet week may be normal, while a missing major release is costly. Monitoring therefore needs baselines, source-specific expectations, and evidence rather than a universal “stale after 24 hours” rule. The objective is to detect loss of visibility without creating alerts every time a team publishes less often.</p>

<h2>Define four separate health dimensions</h2>
<p><strong>Reachability</strong> answers whether the source can be fetched under policy. <strong>Document validity</strong> answers whether the response is a supported, parseable feed. <strong>Content freshness</strong> answers whether the observed update pattern is plausible for this source. <strong>Delivery health</strong> answers whether newly discovered entries reached configured destinations. Combining these into one status hides the difference between publisher outages and your own processing failures.</p>
<p>Track each dimension independently and derive a customer-facing summary from them. A source might be reachable and valid but stale compared with its history. Another might be unreachable now while the last successful snapshot remains current enough for readers. A third might ingest perfectly but have a failing team webhook. The <a href="https://devrss.com/monitoring/">DevRSS monitoring model</a> preserves these distinctions so operators can respond to the right owner.</p>

<h2>Instrument the HTTP exchange</h2>
<p>Record DNS duration, connection duration, TLS duration, time to first byte, total duration, status, redirect chain, response size, content type, cache validators, and whether the document changed. These measurements reveal patterns that a binary check cannot. Increasing connection latency can precede timeouts. Repeated redirects can signal a migration. A sudden size increase can indicate an error page or accidentally embedded media.</p>
<p>Use conditional requests whenever validators are available. HTTP semantics define validators and conditional request behavior; the <a href="https://www.rfc-editor.org/rfc/rfc9110" target="_blank" rel="noopener noreferrer">HTTP Semantics specification</a> is the authoritative reference for interpreting responses and headers. A not-modified response is a successful observation, not a monitoring failure. Store the validator sent and the response received so cache behavior can be audited.</p>

<h2>Validate content type and document shape</h2>
<p>Servers frequently use generic content types, so headers alone should not decide format. Inspect a bounded prefix and parse the document in a safe mode. The monitor should detect when a previously valid feed begins returning HTML, a login page, a bot challenge, or a hosting error. Report the observed document type and a sanitized snippet rather than simply saying “parse failed.” That evidence often makes the publisher-side fix obvious.</p>
<p>Schema validation is useful but should not be the only criterion. Many widely consumed feeds contain minor specification defects. Classify findings as fatal, warning, or informational. A fatal error prevents item extraction; a warning indicates nonconforming data that was handled with a documented fallback. Preserve warning counts over time. A sudden rise after a parser change can expose a regression even when items still appear.</p>

<h2>Monitor entry identity and sequence behavior</h2>
<p>Count total entries, newly observed entries, updated entries, duplicate identifiers, missing identifiers, invalid dates, and items dropped by policy. Track the newest publisher timestamp and newest first-seen timestamp separately. If the document changes but no entries are discovered, the publisher may be editing metadata—or your normalizer may be failing. If every poll creates “new” entries, identifier derivation is unstable.</p>
<p>Create invariants for each source. The feed URL should not oscillate between hosts. Stable entries should not receive new internal IDs. Item count should not jump from dozens to zero without an explicit explanation. Publication dates should not all reset to the current time. These checks catch subtle problems that remain invisible to uptime monitors. Keep a source timeline that shows fetches, document hashes, entry counts, warnings, and deliveries in one view.</p>

<h2>Detect staleness from the source's own history</h2>
<p>A fixed staleness threshold treats a daily security bulletin and a quarterly infrastructure blog the same. Instead, learn a simple baseline from historical intervals between new entries. Use robust statistics such as a median and a high percentile rather than an average that can be distorted by bursts. Require enough observations before declaring an expectation. For new sources, use a generous default or allow the owner to set an expected cadence.</p>
<p>Staleness should be a graded state. “Quiet” can mean the source has exceeded its usual interval but is still reachable and valid. “Suspect” can mean several expected intervals passed or the feed stopped changing while the website shows new content. “Broken” should require stronger evidence such as repeated fetch failures, an invalid replacement document, or a confirmed discrepancy. This reduces noisy alerts while still surfacing important changes.</p>

<h2>Compare the feed with a secondary signal carefully</h2>
<p>For high-value sources, a secondary check can detect a feed that is valid but abandoned. Examples include a release API, a changelog index page, a package registry, or a repository tag stream. Treat this as reconciliation, not scraping the whole site into another source of truth. Compare stable identifiers or canonical URLs and report specific missing items. The secondary signal should have its own rate policy and failure state.</p>
<p>Do not automatically publish content discovered only through an untrusted scrape. Instead, alert that the feed appears behind and show the evidence. A publisher may intentionally exclude certain updates or change URL structure. Human review can confirm whether the feed contract changed. Over-automated reconciliation can create false items and duplicate notifications—the very failures monitoring is supposed to prevent.</p>

<h2>Monitor delivery as a separate service level</h2>
<p>Once an entry is committed, measure queue delay, first delivery attempt, successful delivery time, retry count, destination response class, and dead-letter state. A source freshness dashboard should link to delivery incidents but not merge their status. This lets the ingestion team prove that an update was captured even when a customer endpoint was unavailable.</p>
<p>Define service levels around what you control. For example, measure time from first successful source observation to durable event creation, then from event creation to accepted delivery. Do not promise time from publisher creation when you cannot know when the publisher updated the feed. The <a href="https://devrss.com/syndication/">delivery and syndication layer</a> should expose replay controls so failed destinations can recover without refetching or rewriting source history.</p>

<h2>Alert on transitions, not every failed poll</h2>
<p>Internet sources fail transiently. Alerting on the first timeout creates noise and encourages operators to ignore the system. Use consecutive-failure thresholds, time windows, and severity transitions. A single timeout can be recorded; a source that moves from healthy to degraded after several failed attempts can notify an owner; a source that remains down beyond its usual cadence can escalate. Recoveries should close the same incident rather than creating a disconnected “up” alert.</p>
<p>Group related failures by host and network path. If fifty feeds on one domain fail together, create one incident with affected sources rather than fifty pages. Correlate parser failures by parser version and error signature. Correlate delivery failures by destination host. These groupings turn raw metrics into operational understanding and reduce duplicate investigation.</p>

<h2>Dashboard and runbook checklist</h2>
<ul>
  <li>Show last attempt, last success, last change, last new item, and last successful delivery as separate timestamps.</li>
  <li>Preserve redirect history, validators, response metadata, and bounded diagnostic samples.</li>
  <li>Graph latency, document size, item count, warning count, and discovery lag over time.</li>
  <li>List identifier collisions, invalid dates, dropped items, and parser-version changes.</li>
  <li>Calculate source-specific staleness only after enough history exists.</li>
  <li>Group incidents by host, error signature, parser version, and destination.</li>
  <li>Provide a replay path for parsing and delivery that does not modify source timestamps.</li>
  <li>Link every alert to a runbook explaining likely owners and safe diagnostic steps.</li>
</ul>

<h2>Monitor the contract, not just the endpoint</h2>
<p>Feed monitoring is an exercise in preserving visibility. The endpoint must be reachable, the document must remain interpretable, entry identity must remain stable, and discovered updates must reach their destinations. Each stage produces evidence that helps distinguish a quiet publication from a broken pipeline.</p>
<p>Start with clear health dimensions and a source timeline. Add adaptive staleness and secondary reconciliation only where the value justifies the complexity. A monitor earns trust when it can explain why it is concerned and what changed, not when it produces the most alerts.</p>
]]></content:encoded>
</item>
<item>
<title>WebSub and Webhook Subscriptions: Push Updates Without Wasteful Polling</title>
<link>https://devrss.com/blog/websub-webhook-subscriptions/</link>
<guid isPermaLink="true">https://devrss.com/blog/websub-webhook-subscriptions/</guid>
<pubDate>Wed, 29 Jan 2025 12:00:00 GMT</pubDate>
<category>APIs &amp; Automation</category>
<description><![CDATA[Understand the WebSub publisher-hub-subscriber model, secure callback verification, replay-safe delivery, and when to combine push with polling.]]></description>
<content:encoded><![CDATA[
<p>Polling is a dependable baseline for feeds, but it is inefficient when updates are rare and immediacy matters. A subscriber either checks frequently and wastes requests or checks slowly and accepts delay. WebSub adds a push layer to feed distribution. Publishers advertise a hub, subscribers register callback URLs with that hub, and the hub sends notifications when the topic changes. The feed remains the durable content representation; push acts as the signal that tells subscribers when to fetch or process it.</p>
<p>This separation is important. WebSub is not “send arbitrary JSON to a webhook.” It is a protocol with discovery, subscription verification, leases, signatures, and topic identity. A production implementation should also keep polling as a safety net because subscriptions expire, hubs fail, and publishers can be misconfigured. The strongest design combines event-driven delivery with periodic reconciliation.</p>

<h2>Understand the three roles</h2>
<p>The publisher owns a topic URL, commonly an RSS or Atom feed, and indicates one or more hub URLs. The hub accepts subscription requests and distributes content notifications. The subscriber exposes a callback endpoint and proves that it controls that endpoint during a verification exchange. One service can play multiple roles, but keeping the responsibilities conceptually separate makes security and observability clearer.</p>
<p>The W3C <a href="https://www.w3.org/TR/websub/" target="_blank" rel="noopener noreferrer">WebSub Recommendation</a> describes the protocol and its security considerations. Read the discovery and verification sections carefully. The topic URL is the subscription identity; the hub URL is infrastructure used to manage the subscription. A redirect or feed migration can therefore affect discovery and renewals even when the publisher's home page is unchanged.</p>

<h2>Discovery belongs in the feed workflow</h2>
<p>A consumer can discover hub and topic links in HTTP headers or feed markup. Store the declared topic, resolved feed URL, and hub URL separately. Do not subscribe to an arbitrary hub supplied by a client without applying the same outbound network controls used for feed fetching. Validate schemes, block unsafe addresses, cap redirects, and record the final destination. A malicious hub is still an untrusted remote service.</p>
<p>Discovery should be repeatable because publishers can change hubs. On each successful feed refresh, compare the currently advertised hub and topic links with the active subscription. If they differ, plan a controlled migration rather than immediately deleting the old lease. Keep the old path alive until the new subscription is verified and producing notifications. This avoids blind spots during publisher changes.</p>

<h2>Implement callback verification exactly</h2>
<p>When a subscriber requests a lease, the hub sends a verification request to the callback URL. The callback must validate the mode, topic, challenge, and any lease information, then echo the challenge as required. Only mark the subscription active after verification succeeds. Treat unexpected verification requests as untrusted input, and bind the expected topic and hub to a pending subscription record with an expiration time.</p>
<p>Use a random, unguessable callback token in the URL or an equivalent lookup key. That token identifies the pending subscription without exposing a sequential database ID. Still validate every parameter; possession of the callback URL is not sufficient authorization. Limit request size, require HTTPS for public deployments, and log verification transitions. A lease that cannot be tied to an explicit pending request should not become active.</p>

<h2>Verify delivery authenticity and replay safety</h2>
<p>When the hub supports authenticated content distribution, verify the signature over the raw request body before parsing it. The shared secret should be unique per subscription or tenant, encrypted at rest, and rotated through a controlled resubscription. Compare signatures using a constant-time function. Reject unsupported algorithms and malformed headers. Do not reconstruct the body from parsed fields because any transformation changes the signed bytes.</p>
<p>Authentication does not prevent duplicates. Hubs may retry after timeouts, subscribers may lose a response, and the same changed feed can be sent more than once. Deduplicate using entry identities and content fingerprints after parsing. Also generate an internal delivery-event ID from the subscription and raw payload hash so repeated requests can be recognized before expensive downstream work. Return success only after durable acceptance, not after every final consumer has completed.</p>

<h2>Decide whether the payload is a signal or content</h2>
<p>Some hubs send the updated feed representation in the notification body. Others may provide enough information to prompt a fetch. Your architecture should handle both by normalizing the event into a source-refresh job. If the payload is a supported feed document and passes size and authenticity checks, it can be parsed directly. Otherwise, schedule a guarded fetch of the topic URL. This keeps hub-specific behavior out of the rest of the pipeline.</p>
<p>Even when the full feed arrives, retain conditional validators and periodic polling. A push notification can be dropped, delivered out of order, or contain a truncated representation. A later reconciliation fetch should discover any missing entries without producing duplicates. The <a href="https://devrss.com/monitoring/">feed monitoring system</a> should display both the last push and the last successful reconciliation so operators can distinguish a quiet source from a broken lease.</p>

<h2>Manage leases as scheduled state</h2>
<p>Subscriptions commonly have finite leases. Store the granted expiration time rather than assuming the requested duration. Schedule renewal with a safety margin and jitter, and keep renewal idempotent. If a renewal fails, continue polling and retry according to policy. Do not wait until the exact expiration second; clock differences and network delays can create gaps. A lease state machine is easier to reason about than scattered timestamps.</p>
<p>Useful states include pending verification, active, renewal due, renewal pending, expired, unsubscribing, and failed. Record the hub response and recent transitions. A customer-facing status can simplify these to active, degraded, or inactive, while the operator view keeps the protocol detail. If a customer deletes a source, send an unsubscribe request when appropriate, revoke secrets, stop renewals, and apply retention policy to historical events.</p>

<h2>Protect the callback service</h2>
<p>A public callback endpoint will receive scans and malformed requests. Put strict limits on method, content type, body size, processing time, and concurrent work. Parse only after authentication checks that depend on the raw body. Queue accepted events quickly and return a bounded response. The endpoint should not call customer destinations synchronously or perform long feed parsing while the hub waits.</p>
<p>Rate limiting is nuanced because blocking a legitimate hub can create retries that increase load. Apply per-subscription and per-source controls, detect repeated identical payloads, and maintain an emergency global ceiling. Use observability that preserves privacy: request ID, subscription ID, hub host, body size, signature result, queue result, and response time. Avoid logging full feed content by default.</p>

<h2>When ordinary webhooks are the better choice</h2>
<p>WebSub is ideal when a publisher already exposes a feed and wants interoperable subscription behavior. A direct webhook API may be simpler when one product controls the event schema, authentication, retry policy, and subscriber lifecycle. Direct webhooks can carry small domain events such as “release published” with a stable event ID and a URL for details. They do not need to pretend to be a general syndication protocol.</p>
<p>A platform can support both. WebSub connects to open publishing ecosystems, while signed product webhooks serve customers who want a contractual event schema. Normalize both into the same internal event bus and use the same idempotent delivery machinery. The <a href="https://devrss.com/syndication/">webhook subscription architecture</a> should make the transport visible so clients know whether an event came from a hub notification, a poll reconciliation, or a first-party publisher webhook.</p>

<h2>Operational checklist</h2>
<ul>
  <li>Discover and store topic and hub URLs separately from the submitted feed URL.</li>
  <li>Apply guarded outbound HTTP rules to hubs as well as feeds.</li>
  <li>Bind verification challenges to short-lived pending subscription records.</li>
  <li>Use HTTPS callbacks, unique secrets, raw-body signature verification, and constant-time comparison.</li>
  <li>Accept events durably before returning success and deduplicate at both event and entry levels.</li>
  <li>Renew leases early with jitter and keep polling active as a reconciliation channel.</li>
  <li>Expose lease state, last push, last reconciliation, and recent failures to operators.</li>
  <li>Disable callbacks and rotate secrets promptly when a tenant or source is removed.</li>
</ul>

<h2>Push should improve latency, not weaken correctness</h2>
<p>WebSub can reduce update delay and unnecessary polling while preserving feeds as the portable record. The protocol works best when implemented as a stateful subscription system, not as a generic endpoint that accepts whatever arrives. Verification, lease management, signatures, idempotency, and reconciliation are all part of the feature.</p>
<p>Keep correctness anchored in the feed. A notification says that a topic changed; the canonical entry identities and links determine what changed. With that model, missed or duplicate pushes become recoverable operational events instead of data-loss incidents.</p>
]]></content:encoded>
</item>
<item>
<title>How to Build a Reliable RSS-to-JSON API</title>
<link>https://devrss.com/blog/build-rss-to-json-api/</link>
<guid isPermaLink="true">https://devrss.com/blog/build-rss-to-json-api/</guid>
<pubDate>Fri, 08 Nov 2024 12:00:00 GMT</pubDate>
<category>APIs &amp; Automation</category>
<description><![CDATA[Design a production-ready RSS-to-JSON conversion service with guarded fetching, deterministic normalization, caching, pagination, and transparent error handling.]]></description>
<content:encoded><![CDATA[
<p>An RSS-to-JSON API looks simple in a demo: download XML, parse it, and return an array. Production changes the problem. The service accepts untrusted URLs, encounters inconsistent feed markup, shares capacity across tenants, and must explain whether a failure came from DNS, HTTP, XML, normalization, or rate policy. It also needs stable response semantics so clients are not surprised when a publisher switches from RSS to Atom or edits an old entry.</p>
<p>The most useful API is not a thin wrapper around a parser. It is a controlled ingestion boundary with a documented canonical schema. The conversion step should preserve source identity, expose normalized fields, and report warnings without leaking unsafe markup or network details. This guide focuses on the decisions that make an <a href="https://devrss.com/api/">RSS API</a> predictable under real traffic.</p>

<h2>Start with an explicit request model</h2>
<p>Avoid an endpoint that accepts any URL and immediately performs an unrestricted fetch. Define supported schemes, maximum URL length, redirect policy, authentication rules, and whether callers may use previously registered source IDs instead of raw URLs. A source-registration flow is easier to secure and cache: validate the URL once, assign an internal identifier, and let subsequent requests refer to that identifier. Ad hoc conversion can still exist, but it should have tighter quotas.</p>
<p>Make output options narrow and intentional. Useful parameters include a maximum item count, a continuation cursor, whether sanitized content is included, and a field projection. Parameters should not allow a caller to disable network safeguards or request arbitrary response headers. Return a request ID on every response, and use structured error codes such as <code>source_timeout</code>, <code>unsupported_format</code>, <code>document_too_large</code>, and <code>rate_limited</code>.</p>

<h2>Guard the fetcher as a security boundary</h2>
<p>The fetcher is the highest-risk component because a caller influences where your server connects. Resolve the host and reject loopback, private, link-local, multicast, and cloud metadata ranges. Repeat the check after redirects and DNS changes. Allow only HTTP and HTTPS. Apply connection, header, body, and total request deadlines. Bound the decompressed body size because a small compressed response can expand dramatically in memory.</p>
<p>Run fetch workers with minimal network permissions and no access to internal service credentials. Do not forward caller headers wholesale. Use a clear user agent, accept only relevant media types, and limit redirects. Capture response status, selected headers, timing phases, and a bounded diagnostic sample. Never return raw internal addresses or stack traces to clients. Security controls should apply whether the request came from a free trial, an administrator, or an internal job.</p>

<h2>Create a canonical response schema</h2>
<p>Your schema should remain stable when the input format changes. A feed object can contain a source ID, requested URL, resolved URL, title, description, home-page URL, feed URL, language, icon, authors, format, fetched time, and warnings. Each item can contain a normalized ID, source-provided ID, URL, external URL, title, summary, content HTML, content text, dates, authors, tags, attachments, and an extension map. Keep publisher dates separate from observation times.</p>
<p>JSON Feed is a useful reference because it expresses syndication concepts directly in JSON. The <a href="https://www.jsonfeed.org/version/1.1/" target="_blank" rel="noopener noreferrer">JSON Feed 1.1 specification</a> distinguishes item IDs, content HTML, content text, summaries, dates, authors, tags, and attachments. Your API does not have to reproduce that schema exactly, but matching established semantics reduces invention. Document every field's nullability, normalization rule, and ordering behavior.</p>
<pre><code>{
  "source": {
    "id": "src_01J...",
    "format": "rss2",
    "requested_url": "https://example.dev/feed",
    "resolved_url": "https://example.dev/rss.xml",
    "fetched_at": "2026-10-23T18:42:12Z",
    "warnings": []
  },
  "items": [{
    "id": "itm_01J...",
    "source_id": "release-4.8.0",
    "url": "https://example.dev/releases/4-8-0",
    "title": "Version 4.8.0",
    "date_published": "2026-10-20T09:00:00Z",
    "first_seen_at": "2026-10-20T09:04:31Z"
  }],
  "next_cursor": null
}</code></pre>

<h2>Normalize deterministically</h2>
<p>Normalization must produce the same result from the same document and configuration. Establish precedence rules for content fields, author fields, links, dates, and identifiers. Resolve relative URLs against the correct base URI. Convert dates to a standard representation while preserving the original string when parsing was uncertain. Sanitize HTML with a fixed allowlist and rewrite or remove unsafe URLs. Normalize whitespace only where it does not change meaningful code or preformatted content.</p>
<p>Warnings are better than silent guesses. If an item has an invalid date, return the item with a warning and a null normalized date if your contract allows it. If a publisher reuses a GUID, record the collision and fall back according to a documented rule. Expose the input format and parser version in diagnostic metadata so a changed result can be traced after an upgrade. The core response should stay concise, while a debug mode available to authorized users can include normalization decisions.</p>

<h2>Separate conversion from freshness</h2>
<p>A synchronous conversion endpoint can either fetch live on every request or serve a cached snapshot. Live fetches are easy to explain but slow, expensive, and vulnerable to publisher outages. Snapshots are fast and protect publishers, but clients need to know how old the data is. Include <code>fetched_at</code>, <code>next_scheduled_fetch</code>, and a freshness state. Let clients request a refresh through a controlled asynchronous action rather than turning every read into network work.</p>
<p>Use conditional requests and store validators such as ETag and Last-Modified. A not-modified response should update observation metadata without reparsing the document. Cache normalized output by source version, not just URL, so multiple tenants can share safe fetch work while keeping authorization and filtering separate. The <a href="https://devrss.com/monitoring/">feed monitoring layer</a> should own schedules and source health; the API read path should remain dependable even when a publisher is temporarily unavailable.</p>

<h2>Design pagination around stable ordering</h2>
<p>Feeds are usually small, but normalized history can grow. Offset pagination becomes unstable when new entries arrive at the front. Use an opaque cursor derived from a stable sort key such as first-seen time plus internal item ID. State whether ordering follows publisher publication time, update time, or observation time. For synchronization, observation order is often safest because it reflects when your system learned about an item.</p>
<p>Support incremental reads with a cursor or <code>since_id</code> token rather than asking clients to compare whole arrays. Keep cursor semantics independent of display filters where possible. When an old item is edited, decide whether it reappears in the change stream and expose an event type such as created, updated, or deleted. A conversion endpoint and a change endpoint serve different needs; combining them without explicit semantics creates missed updates.</p>

<h2>Make errors useful and cacheable</h2>
<p>Map failures to stable HTTP statuses and API codes. Invalid caller input is different from an unreachable publisher. A source timeout might return a gateway-style status for a live conversion, while a snapshot read can still return the last successful data plus a stale warning. Include retry guidance only when it is meaningful. A <code>retry_after</code> value should come from policy or a source response, not a generic guess.</p>
<p>Negative caching protects both your system and publishers. If a hostname does not resolve, or a source repeatedly returns a permanent not-found response, delay the next network attempt. Do not cache authorization failures across tenants. Track error transitions so an operator can see whether a source is flapping, permanently moved, or newly malformed. The status endpoint should summarize health without exposing private network details.</p>

<h2>Version the contract, not every implementation detail</h2>
<p>Use a clear API version in the URL or media type and define what counts as a breaking change. Adding an optional field is usually compatible; changing identifier derivation or date precedence is not. Parser upgrades can alter edge-case behavior even when the public schema stays the same, so run a fixture corpus through old and new versions and compare normalized output. Release notes should describe semantic changes, not only library versions.</p>
<p>Keep the source document available for a short diagnostic retention period subject to privacy and storage policy. That lets you replay a specific fetch when a client reports a regression. Do not promise perfect fidelity if you sanitize or truncate content; say exactly what the API preserves. Transparent limitations are part of a reliable contract.</p>

<h2>Production checklist</h2>
<ul>
  <li>Register or validate sources before granting recurring fetch capacity.</li>
  <li>Block unsafe address ranges before every connection and redirect.</li>
  <li>Limit time, bytes, decompression, redirects, and parser complexity.</li>
  <li>Normalize into a versioned schema with explicit precedence rules.</li>
  <li>Return warnings for recoverable source defects and stable codes for failures.</li>
  <li>Store conditional validators and separate cached reads from controlled refreshes.</li>
  <li>Use cursor-based synchronization and document update behavior.</li>
  <li>Protect tenants with quotas, request IDs, audit logs, and per-source observability.</li>
</ul>

<h2>Conversion is the beginning, not the product</h2>
<p>The value of an RSS-to-JSON API comes from dependable boundaries. It fetches safely, interprets several formats consistently, and gives clients a stable model that includes provenance and freshness. Once that foundation exists, filtering, webhook delivery, analytics, and custom developer feeds become composable features rather than special cases.</p>
<p>Build the simplest version that can explain itself. A client should be able to tell what was requested, where the request resolved, when the source was observed, which format was parsed, what warnings were applied, and how to continue reading. That clarity is what turns a converter into infrastructure.</p>
]]></content:encoded>
</item>
<item>
<title>RSS vs Atom vs JSON Feed for Developer Platforms</title>
<link>https://devrss.com/blog/rss-atom-json-feed-comparison/</link>
<guid isPermaLink="true">https://devrss.com/blog/rss-atom-json-feed-comparison/</guid>
<pubDate>Thu, 27 Jun 2024 12:00:00 GMT</pubDate>
<category>Guides</category>
<description><![CDATA[Compare the data models, discovery patterns, extension mechanisms, and operational tradeoffs of RSS 2.0, Atom, and JSON Feed.]]></description>
<content:encoded><![CDATA[
<p>Choosing between RSS, Atom, and JSON Feed is less about declaring a winner and more about deciding where compatibility, metadata precision, and implementation convenience matter in your system. All three formats describe a feed and a sequence of entries. All can carry titles, links, dates, identifiers, authors, and content. The differences show up in how strictly those concepts are defined, how extensions are expressed, and which tooling ecosystems already understand the document.</p>
<p>A developer platform rarely controls every publisher and every consumer. That makes format support an interoperability decision. A public feed should meet readers where they are, while an internal API can use the representation that is easiest for its clients. The most durable architecture separates the external syndication format from the internal canonical model. You can then accept several feed types, normalize them once, and expose a consistent <a href="https://devrss.com/api/">RSS-to-JSON API</a> without pretending the source formats are identical.</p>

<h2>The common model underneath the syntax</h2>
<p>Each format has a document-level object and entry-level objects. At the document level you normally find a title, canonical or home-page links, a description, update information, and authorship metadata. At the entry level you find an identifier, title, link, publication or modification dates, summary, full content, and sometimes media attachments. A robust consumer should map these concepts into explicit internal fields and retain a source-specific extension bag for information that does not fit the common core.</p>
<p>Do not normalize by simply renaming XML tags into JSON keys. The meaning of an RSS <code>guid</code>, an Atom <code>id</code>, and a JSON Feed item <code>id</code> overlaps, but publisher behavior varies. Atom distinguishes alternate links from self links and defines text constructs precisely. RSS permits a broad extension ecosystem through XML namespaces. JSON Feed is direct for JavaScript and typed API clients but may be less widely discovered by older readers. Semantic normalization requires rules and tests, not only parsing.</p>

<h2>RSS 2.0: maximum compatibility, flexible interpretation</h2>
<p>RSS 2.0 remains the broadest compatibility choice. Readers, podcast applications, CMS platforms, changelog tools, and aggregators commonly recognize it. Its core is compact and approachable: a channel contains items, and each item can include a title, link, description, publication date, GUID, categories, and enclosure. XML namespaces let publishers add domain-specific elements without changing the core version. That flexibility has enabled podcast metadata, media extensions, and many vendor-specific additions.</p>
<p>The operational cost is ambiguity. Real feeds vary in date formats, HTML encoding, GUID stability, and use of descriptions versus content extensions. A consumer must be tolerant without becoming unpredictable. Treat the core schema as a baseline, preserve namespaces, and document precedence rules. For example, decide whether full-content extensions outrank <code>description</code>, how relative links are resolved, and whether a non-permalink GUID is ever treated as a URL. RSS is easy to start consuming and requires discipline to consume consistently at scale.</p>

<h2>Atom: a more precise syndication vocabulary</h2>
<p>Atom was designed to remove several ambiguities found in earlier feed practice. The <a href="https://www.rfc-editor.org/info/rfc4287/" target="_blank" rel="noopener noreferrer">Atom Syndication Format in RFC 4287</a> specifies an XML vocabulary with required feed and entry identifiers, defined date constructs, typed content, and link relations. An entry can have multiple links that state their purpose, such as an alternate human-readable page, a related resource, or an enclosure. The feed can identify its own retrieval URL with a self link, which helps consumers track migrations and document identity.</p>
<p>That precision is useful for developer platforms that care about synchronization and provenance. Atom's <code>updated</code> field is required, and identifiers are intended to remain stable even when URLs change. Content can be text, HTML, XHTML, or externally referenced, with rules for each form. The tradeoff is more XML ceremony and a smaller mainstream publishing footprint than RSS in some domains. When you control both sides, Atom is a strong contract. When you aggregate the open web, supporting Atom is essential but rarely sufficient by itself.</p>

<h2>JSON Feed: native ergonomics for modern clients</h2>
<p>JSON Feed expresses the same broad syndication model in JSON. It is pleasant to consume in browser, serverless, mobile, and typed API environments because clients avoid XML-specific tooling and can work with familiar objects. Fields distinguish HTML content from plain text, attachments are structured arrays, and the document can expose home-page and feed URLs directly. The format also fits API gateways and observability tools that already inspect JSON payloads.</p>
<p>The main consideration is ecosystem reach. A JSON Feed endpoint may not be automatically recognized by older readers or publishing plugins, and some sites expose only RSS or Atom. A platform can publish JSON Feed as an additional representation without abandoning established formats. Use HTML autodiscovery for each available feed and send the appropriate media type. For internal products, JSON Feed can be a clean client contract, but preserve the original source type and fields so consumers can understand where the normalized data came from.</p>

<h2>Comparison by implementation concern</h2>
<div class="table-wrap"><table>
<thead><tr><th>Concern</th><th>RSS 2.0</th><th>Atom</th><th>JSON Feed</th></tr></thead>
<tbody>
<tr><td>Typical syntax</td><td>XML channel and item elements</td><td>XML feed and entry elements</td><td>JSON object and items array</td></tr>
<tr><td>Compatibility</td><td>Very broad across readers and podcasts</td><td>Strong across technical aggregators</td><td>Growing, strongest in developer tooling</td></tr>
<tr><td>Identity model</td><td>GUID is optional and variably used</td><td>Feed and entry IDs are required</td><td>Item IDs are required</td></tr>
<tr><td>Link semantics</td><td>Simple core link plus extensions</td><td>Multiple typed link relations</td><td>Dedicated URL fields and attachments</td></tr>
<tr><td>Extension style</td><td>XML namespaces</td><td>XML namespaces and defined extension rules</td><td>Custom keys conventionally prefixed</td></tr>
<tr><td>Client ergonomics</td><td>Excellent tooling, XML handling required</td><td>Precise model, XML handling required</td><td>Direct for JSON-native applications</td></tr>
</tbody></table></div>
<p>The table is a starting point, not a substitute for source fixtures. Build a corpus that includes the publishers you expect to support. Compare how each parser exposes author names, multiple links, content, categories, and attachments. Record any information lost during normalization. A format choice that looks elegant on paper can fail if your actual producers omit the fields your product depends on.</p>

<h2>Discovery, media types, and canonical URLs</h2>
<p>Format support is only useful when clients can find the feed. Add autodiscovery links to the HTML head for every representation you publish. Provide a visible feed link in the footer or documentation. Serve each document with a suitable content type and a stable URL. Do not change the feed URL every time the site structure changes; redirect permanently when a move is unavoidable and keep document-level self references accurate where the format supports them.</p>
<p>Consumers should not rely solely on a file extension or server header. Inspect the payload safely and identify the document root or JSON version field. Keep the requested URL, final response URL, declared feed URL, and home-page URL as distinct values. Those URLs answer different questions: where the subscription was configured, where the server redirected, what the document considers its own address, and where a person should read the publication.</p>

<h2>Choose a publication strategy</h2>
<p>For a public developer publication, RSS 2.0 is usually the first format to ship because it reaches the widest set of tools. Add Atom when you need its link and identity semantics or when your framework already produces it well. Add JSON Feed when your audience includes API consumers, client applications, or developer portals that benefit from JSON-native parsing. Multiple representations should be generated from the same canonical content records so titles, dates, IDs, and links remain aligned.</p>
<p>For an aggregator, support all three at the ingestion boundary and normalize into one internal model. Keep format-specific adapters small and deterministic. The core system should not contain scattered checks for RSS versus Atom; those decisions belong in the adapter. When a client requests output, decide whether to expose your canonical JSON schema, a compliant feed representation, or both. The <a href="https://devrss.com/feeds/">developer feeds overview</a> shows how this separation keeps publishers and consumers loosely coupled.</p>

<h2>Test semantics, not just syntax</h2>
<ul>
  <li>Verify that stable entry identities remain stable after title, content, or URL edits.</li>
  <li>Test feeds with missing optional dates, multiple authors, multiple links, and namespace extensions.</li>
  <li>Confirm that HTML, plain text, and externally referenced content are handled according to their declared type.</li>
  <li>Resolve relative URLs against the correct base and retain the original value for diagnostics.</li>
  <li>Exercise redirects, conditional requests, compression, unexpected encodings, and documents served with generic media types.</li>
  <li>Round-trip your own published content through independent readers and validators.</li>
</ul>

<h2>The right answer is usually “normalize all three”</h2>
<p>RSS, Atom, and JSON Feed solve the same category of problem with different balances of compatibility, rigor, and ergonomics. A publisher should choose the smallest set that serves its audience reliably. A developer platform that aggregates external sources should avoid making customers choose on behalf of publishers. Accept the common formats, retain provenance, and present one predictable API.</p>
<p>That approach turns the format debate into an adapter problem. The product can focus on freshness, filtering, monitoring, and delivery while the boundary layer respects each specification. Interoperability improves when you preserve differences deliberately rather than erasing them accidentally.</p>
]]></content:encoded>
</item>
<item>
<title>Developer RSS Feeds: A Practical Architecture Guide</title>
<link>https://devrss.com/blog/developer-rss-feeds-architecture-guide/</link>
<guid isPermaLink="true">https://devrss.com/blog/developer-rss-feeds-architecture-guide/</guid>
<pubDate>Thu, 15 Feb 2024 12:00:00 GMT</pubDate>
<category>Guides</category>
<description><![CDATA[A systems-focused guide to using RSS as a durable integration layer for engineering blogs, changelogs, release notes, security advisories, and internal knowledge streams.]]></description>
<content:encoded><![CDATA[
<p>Developer RSS feeds are not a nostalgic publishing feature. They are a compact, inspectable contract for moving ordered updates between systems that do not share a database, deployment cycle, or vendor. An engineering organization can use feeds to expose release notes, incident summaries, package changes, security advisories, documentation updates, or curated technical reading without forcing every consumer to learn a custom API. The format is simple enough to debug with a browser and structured enough to support automation.</p>
<p>The architectural value appears when you stop treating a feed as a page of links and start treating it as an append-oriented event surface. A feed has a stable identity, entries have stable identifiers, timestamps establish ordering, and links point back to canonical resources. Those properties are enough to build useful ingestion, filtering, alerting, and archival pipelines. The goal is not to replace every event bus. It is to create a low-friction boundary where publishers and consumers can evolve independently.</p>

<h2>Why feeds fit developer information</h2>
<p>Developer information is fragmented by design. Product teams publish changelogs, open-source projects publish releases, security teams publish advisories, and developer relations teams publish tutorials. The update cadence is irregular, and the audience often wants to follow many sources at once. Email is difficult to automate, web scraping depends on presentation markup, and a bespoke API can be disproportionate for a stream that mostly says “a new item exists.” A feed gives each publisher a small, predictable surface that aggregators can poll and cache.</p>
<p>The model also respects the difference between notification and source of truth. A feed entry can carry enough context to classify an update while linking to the authoritative page for the complete record. That reduces duplicated content and lets publishers correct the canonical page without rebuilding every downstream integration. On <a href="https://devrss.com/feeds/">DevRSS developer feed workflows</a>, this boundary is explicit: ingest the metadata needed for routing, retain the canonical URL, and avoid pretending the normalized record is the original source.</p>

<h2>Define the feed contract before the parser</h2>
<p>A reliable implementation begins with a contract, not a library selection. Decide which feed types you accept, how large a document may be, which content types are allowed, whether redirects are followed, how long network requests may run, and how identifiers are derived when publishers omit a GUID. Write down how you treat date fields, relative links, HTML fragments, enclosures, namespaces, and duplicate entries. These decisions determine whether two workers produce the same normalized item from the same source.</p>
<p>For RSS 2.0, the best starting point is the <a href="https://www.rssboard.org/rss-specification" target="_blank" rel="noopener noreferrer">RSS 2.0 specification</a>. Read it as a data-contract document rather than a tutorial. Note which channel and item elements are required, which are optional, and where extensions can appear. Then create an internal canonical model that is deliberately smaller than the union of every possible extension. Preserve unknown fields separately when they may matter later, but keep routing logic dependent on a stable core.</p>

<h2>Use a staged ingestion pipeline</h2>
<p>A feed service is easier to operate when fetching, parsing, normalization, storage, and delivery are separate stages. The fetcher should know about HTTP but not business rules. The parser should transform bytes into a format-aware document. The normalizer should map that document into your canonical entry model. Storage should enforce uniqueness and record observation history. Delivery should fan out only after an item has been committed. This separation lets you retry a webhook without refetching the publisher or replay normalization after a parser upgrade.</p>
<p>A practical pipeline usually contains the following stages:</p>
<ol>
  <li><strong>Discover:</strong> resolve the submitted URL, follow a bounded number of redirects, and record the final canonical feed URL.</li>
  <li><strong>Fetch:</strong> send conditional headers, enforce timeouts and byte limits, and store response metadata.</li>
  <li><strong>Parse:</strong> detect RSS, Atom, or JSON Feed from content rather than trusting a filename.</li>
  <li><strong>Normalize:</strong> resolve links, sanitize embedded markup, standardize timestamps, and retain source-specific fields.</li>
  <li><strong>Deduplicate:</strong> compare stable identifiers first, then canonical URLs, then a documented fallback fingerprint.</li>
  <li><strong>Route:</strong> apply tenant filters, topic rules, and destination policies to newly committed items.</li>
  <li><strong>Deliver:</strong> enqueue webhooks, digests, or internal events with idempotency keys.</li>
</ol>
<p>The staged model also produces better observability. A single “feed failed” metric is not actionable. Separate counters for DNS failure, timeout, non-success response, unsupported media type, XML parse error, missing identity, duplicate item, and downstream delivery failure tell an operator where to look. They also keep publisher quality problems from being confused with your own infrastructure problems.</p>

<h2>Model identity, freshness, and change explicitly</h2>
<p>Entries need a durable internal identity. Prefer a publisher-provided GUID or Atom entry ID when it is stable. A canonical item URL can be a secondary key, but URLs sometimes change during migrations. A content hash is useful for detecting edits but should not usually become the primary identifier because a minor correction would create a new item. Store the original identity, the normalized identity, the first-seen time, the latest-seen time, and a content fingerprint so you can distinguish a new entry from a revised one.</p>
<p>Freshness is not the same as publication time. An old article can appear for the first time after a feed is repaired, and a newly published entry can be observed late because the source was unavailable. Keep publisher timestamps and observation timestamps as separate fields. For monitoring, calculate lag from the first successful observation, not from whichever date happens to be present. The <a href="https://devrss.com/monitoring/">feed monitoring model</a> should report source reachability, document change, entry discovery, and delivery completion as different signals.</p>

<h2>Plan for hostile and merely messy input</h2>
<p>Public feed URLs are untrusted network input. Protect the fetcher against server-side request forgery by blocking private, loopback, link-local, and metadata-service addresses before every connection and after DNS resolution. Limit redirects, decompress with a ceiling, reject unexpected schemes, and keep outbound network permissions narrow. XML parsing must disable external entities and avoid features that can expand small documents into excessive memory use. HTML content should be sanitized before it enters a dashboard or webhook.</p>
<p>Most failures are not malicious; they are inconsistent. Publishers emit invalid dates, change GUID rules, reuse titles, omit schemes from links, or serve an HTML error page with a successful status code. Capture a bounded sample of the failing response, expose a human-readable diagnosis, and keep the raw document long enough to reproduce parser behavior. A strict validation mode is valuable for publishers, while a tolerant ingestion mode is often better for subscribers. Do not silently repair data without recording what changed.</p>

<h2>Design the operational loop</h2>
<p>Every source should have a next-fetch time, a failure count, a recent latency profile, and a health state. Use successful change history to adapt polling: active feeds can be checked more frequently, while dormant feeds can back off. Add jitter so thousands of sources do not align on the same minute. Respect caching headers and conditional requests. After repeated failures, slow down rather than hammering the publisher, but continue occasional probes so a recovered feed can return automatically.</p>
<p>Delivery needs its own loop. Assign each destination an idempotency key based on tenant, entry, and destination. Retry transient errors with exponential backoff, stop retrying permanent client errors according to policy, and provide a dead-letter view that includes the payload and response. Keep delivery attempts separate from source health; a broken webhook endpoint should not mark the publisher feed unhealthy. The <a href="https://devrss.com/syndication/">syndication architecture</a> can then support webhooks, team digests, and downstream APIs without coupling them to the poller.</p>

<h2>A practical implementation sequence</h2>
<ul>
  <li>Write the canonical entry schema and identity rules before integrating a parser.</li>
  <li>Implement a guarded HTTP fetcher with conditional requests, timeouts, redirect limits, and address filtering.</li>
  <li>Add format detection and fixtures for valid, malformed, oversized, redirected, and compressed feeds.</li>
  <li>Store raw fetch metadata alongside normalized items so incidents can be reproduced.</li>
  <li>Commit items before dispatching any notification, and use a queue between storage and delivery.</li>
  <li>Expose per-stage metrics and a source timeline rather than one aggregated health flag.</li>
  <li>Test identifier changes, edited entries, feed moves, time-zone errors, and out-of-order publication dates.</li>
  <li>Document retention, tenant isolation, and webhook signing before onboarding production customers.</li>
</ul>

<h2>Build for portability, not novelty</h2>
<p>The strongest developer feed system is intentionally boring at its boundaries. It accepts established formats, preserves canonical links, applies explicit normalization rules, and makes failures visible. Its sophistication belongs in operational discipline: safe fetching, deterministic parsing, stable identity, adaptive scheduling, and idempotent delivery. Those qualities let a small syndication format become dependable infrastructure.</p>
<p>Start with one feed and trace it through every stage. Ask whether an operator can explain what happened, whether a consumer can replay the result, and whether a publisher outage is isolated from delivery. Once those answers are clear, scaling to hundreds or thousands of sources is primarily a scheduling and observability problem rather than a reinvention of the content model.</p>
]]></content:encoded>
</item>
</channel>
</rss>
