<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: WEBVOGUE Technologies</title>
    <description>The latest articles on DEV Community by WEBVOGUE Technologies (@webvogue).</description>
    <link>https://dev.to/webvogue</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4122263%2F7fac9ab7-7f80-4e4d-8cdb-58cada704635.png</url>
      <title>DEV Community: WEBVOGUE Technologies</title>
      <link>https://dev.to/webvogue</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/webvogue"/>
    <language>en</language>
    <item>
      <title>What I learned checking 65 client domains with RDAP instead of WHOIS</title>
      <dc:creator>WEBVOGUE Technologies</dc:creator>
      <pubDate>Sat, 12 Sep 2026 15:17:20 +0000</pubDate>
      <link>https://dev.to/webvogue/what-i-learned-checking-65-client-domains-with-rdap-instead-of-whois-5cd3</link>
      <guid>https://dev.to/webvogue/what-i-learned-checking-65-client-domains-with-rdap-instead-of-whois-5cd3</guid>
      <description>&lt;p&gt;I built a domain expiry monitor, and to test it against something other than my own domains I pointed it at the "our work" pages of 30 US web design agencies — the client sites they link to publicly — and checked every domain against its registry.&lt;/p&gt;

&lt;p&gt;The results were mildly alarming, but the interesting part was everything that went wrong on the way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results first
&lt;/h2&gt;

&lt;p&gt;65 client domains across 14 agencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1 had already expired&lt;/strong&gt;, 60 days earlier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 were inside 45 days&lt;/strong&gt;, the nearest with 3 days left&lt;/li&gt;
&lt;li&gt;the rest ran from 46 days to several years out, clustering at 3–6 months&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not naming the agencies or the domains. Telling someone privately that their client's domain is about to lapse is useful; publishing it is exposing somebody's client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why RDAP and not WHOIS
&lt;/h2&gt;

&lt;p&gt;WHOIS returns free text in roughly as many formats as there are registrars. RDAP (RFC 7482/9083) returns JSON, over HTTPS, with a documented shape.&lt;/p&gt;

&lt;p&gt;The single nicest property: &lt;strong&gt;a 404 is an answer.&lt;/strong&gt; In RDAP it means "this domain is not registered", which is precisely the event worth alerting on. No string matching on "No match for domain".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;domain/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/rdap+json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;registered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Three things that will bite you
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The registrar's name is hidden in a jCard
&lt;/h3&gt;

&lt;p&gt;There's no &lt;code&gt;registrar&lt;/code&gt; field. The registrar is an entity with the &lt;code&gt;registrar&lt;/code&gt; role, and its human-readable name lives inside a vCard-in-JSON structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entities&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;registrar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;vcardArray&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;   &lt;span class="c1"&gt;// "GoDaddy.com, LLC"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vcardArray[1]&lt;/code&gt; is an array of &lt;code&gt;["fn", {}, "text", "GoDaddy.com, LLC"]&lt;/code&gt; triples. That awkwardness is why so many tools show you IANA registrar ID 146 instead of a name — and "where do I renew this" is the actual operational question for anyone holding domains across several registrars.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. IANA's bootstrap does not cover every TLD
&lt;/h3&gt;

&lt;p&gt;The bootstrap file maps TLDs to RDAP servers, and &lt;code&gt;.de&lt;/code&gt;, &lt;code&gt;.co&lt;/code&gt; and &lt;code&gt;.gg&lt;/code&gt; simply aren't in it. There's no authoritative endpoint to ask.&lt;/p&gt;

&lt;p&gt;The right response is to say so. "The registry publishes no expiry date" is a fact about the registry, not about the domain, and it's more useful to a user than a number you inferred from somewhere else. Any tool that confidently shows an expiry date for a &lt;code&gt;.de&lt;/code&gt; is guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Public suffixes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cartwright.co.uk&lt;/code&gt; naively reduces to &lt;code&gt;co.uk&lt;/code&gt;. Query that and you get the registry's own record, then cheerfully report the registry's expiry date as your user's domain expiry. The full Public Suffix List is thousands of entries; even a small hardcoded set of &lt;code&gt;co.uk&lt;/code&gt;, &lt;code&gt;com.au&lt;/code&gt;, &lt;code&gt;co.nz&lt;/code&gt; and friends prevents the embarrassing version of this bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that nearly discredited the whole thing
&lt;/h2&gt;

&lt;p&gt;My first extractor took every &lt;code&gt;href&lt;/code&gt; on the page. For one agency it confidently reported &lt;code&gt;licdn.com&lt;/code&gt;, &lt;code&gt;hotjar.com&lt;/code&gt;, &lt;code&gt;addtoany.com&lt;/code&gt; and &lt;code&gt;gmpg.org&lt;/code&gt; as their clients.&lt;/p&gt;

&lt;p&gt;All four came from the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;: &lt;code&gt;&amp;lt;link rel="preconnect"&amp;gt;&lt;/code&gt; hints and the XFN profile link WordPress writes into every page.&lt;/p&gt;

&lt;p&gt;I started adding them to a blocklist, then stopped — every new analytics vendor adds another entry, and the blocklist can never be finished. The structural rule can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Only anchors. A person clicks anchors, and only anchors point at clients.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&amp;lt;a&lt;/span&gt;&lt;span class="se"&gt;\b[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;?\b&lt;/span&gt;&lt;span class="sr"&gt;href&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*=&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;"&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;"&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;"|'&lt;/span&gt;&lt;span class="se"&gt;([^&lt;/span&gt;&lt;span class="sr"&gt;'&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;'|&lt;/span&gt;&lt;span class="se"&gt;([^\s&lt;/span&gt;&lt;span class="sr"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;))&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line, and the entire category of failure disappears.&lt;/p&gt;

&lt;p&gt;A related one: an agency's &lt;em&gt;second&lt;/em&gt; domain isn't the agency's client. One firm's site was at &lt;code&gt;atendesigngroup.com&lt;/code&gt; and they also owned &lt;code&gt;aten.io&lt;/code&gt;; another at &lt;code&gt;openmediafoundation.org&lt;/code&gt; owned &lt;code&gt;open.media&lt;/code&gt;. Matching only the exact domain you crawled isn't enough — comparing the leading label catches both.&lt;/p&gt;

&lt;h2&gt;
  
  
  About a third of sites hard-block automated requests
&lt;/h2&gt;

&lt;p&gt;This one changed the architecture. I wrote the fetcher with &lt;code&gt;fetch&lt;/code&gt;, and got &lt;strong&gt;403 on every single site&lt;/strong&gt;, including with a complete set of genuine browser headers. Not a Cloudflare JS challenge you can wait out — a flat 403, identical in headless Chrome and in a real headed one. It's blocking by IP reputation.&lt;/p&gt;

&lt;p&gt;I switched to driving a real browser, which fixed it for about two thirds of sites. That turned out to matter for a second reason I hadn't considered: &lt;strong&gt;most agency portfolios render their client grid client-side&lt;/strong&gt;, in Webflow or React. Even a successful &lt;code&gt;fetch&lt;/code&gt; would have returned markup with no client links in it, and I'd have spent a day blaming my extractor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unglamorous finding
&lt;/h2&gt;

&lt;p&gt;Only &lt;strong&gt;14 of 30&lt;/strong&gt; agency sites linked three or more client domains at all. The rest show client work as screenshots with no anchor.&lt;/p&gt;

&lt;p&gt;That's a lot of backlinks being left on the floor.&lt;/p&gt;




&lt;p&gt;If you want to look at what I built with this, it's at &lt;a href="https://dropperch.com" rel="noopener noreferrer"&gt;dropperch.com&lt;/a&gt; — free tier monitors domains. But the RDAP notes above are the genuinely reusable part, and they're yours whether or not you ever look at it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>api</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
