[object Object]

← back to Domain Sniper

register: fix Namecheap false-positive when API ok but registration failed

667159d4a7a5e365c6c2d748cdc2df90e2f0a92b · 2026-05-12 18:46:31 -0700 · steve

Eyeball audit of the Namecheap response parser caught a real bug:
the success check only matched the open tag '<DomainCreateResult'
regardless of its Registered= attribute. Namecheap API uses a two-
layer status: outer Status="OK" means 'API call accepted', inner
DomainCreateResult Registered="true" means 'domain actually
registered'. A request can succeed at layer 1 and fail at layer 2
(e.g. domain unavailable, registrant invalid, insufficient funds)
without raising an <Error> — the prior code would report success
on such cases.

New logic requires BOTH:
  apiOk = Status="OK"
  regOk = DomainCreateResult ... Registered="true"
plus a clearer detail message that distinguishes 'api failed'
from 'api ok but registration declined'.

watch-outbound.js audit: clean (loopback-only, no hardcoded creds,
35-pattern INTEL allowlist comprehensive). NameSilo + GoDaddy
parsers also reviewed — NameSilo correct, GoDaddy intentionally
NOT_WIRED to avoid silent billing.

Files touched

Diff

commit 667159d4a7a5e365c6c2d748cdc2df90e2f0a92b
Author: steve <steve@designerwallcoverings.com>
Date:   Tue May 12 18:46:31 2026 -0700

    register: fix Namecheap false-positive when API ok but registration failed
    
    Eyeball audit of the Namecheap response parser caught a real bug:
    the success check only matched the open tag '<DomainCreateResult'
    regardless of its Registered= attribute. Namecheap API uses a two-
    layer status: outer Status="OK" means 'API call accepted', inner
    DomainCreateResult Registered="true" means 'domain actually
    registered'. A request can succeed at layer 1 and fail at layer 2
    (e.g. domain unavailable, registrant invalid, insufficient funds)
    without raising an <Error> — the prior code would report success
    on such cases.
    
    New logic requires BOTH:
      apiOk = Status="OK"
      regOk = DomainCreateResult ... Registered="true"
    plus a clearer detail message that distinguishes 'api failed'
    from 'api ok but registration declined'.
    
    watch-outbound.js audit: clean (loopback-only, no hardcoded creds,
    35-pattern INTEL allowlist comprehensive). NameSilo + GoDaddy
    parsers also reviewed — NameSilo correct, GoDaddy intentionally
    NOT_WIRED to avoid silent billing.
---
 register.js | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/register.js b/register.js
index 75218eb..24de98e 100644
--- a/register.js
+++ b/register.js
@@ -84,9 +84,21 @@ async function registerNamecheap(domain) {
     { hostname: 'api.namecheap.com', path: '/xml.response?' + params.toString(), method: 'GET' },
     null,
   );
-  const success = /Status="OK"/.test(r.body) && /<DomainCreateResult/.test(r.body);
+  // Two-layer success check: the API call must have Status="OK" AND the
+  // inner <DomainCreateResult Registered="true"...> must confirm the actual
+  // registration. Status="OK" alone only means "API request was valid" — it
+  // does NOT mean the domain was registered (the create result can still
+  // come back Registered="false" with the call still nominally OK).
+  const apiOk = /Status="OK"/.test(r.body);
+  const regOk = /<DomainCreateResult\b[^>]*\bRegistered="true"/i.test(r.body);
+  const success = apiOk && regOk;
   const errMatch = r.body.match(/<Error[^>]*>([^<]+)<\/Error>/);
-  return { success, code: success ? 'OK' : 'ERROR', detail: errMatch ? errMatch[1] : (success ? 'registered' : 'unknown'), http: r.status };
+  let detail;
+  if (success) detail = 'registered';
+  else if (!apiOk && errMatch) detail = errMatch[1];
+  else if (apiOk && !regOk) detail = 'api ok but DomainCreateResult.Registered != true';
+  else detail = 'unknown';
+  return { success, code: success ? 'OK' : 'ERROR', detail, http: r.status };
 }
 
 async function registerGodaddy(domain) {

← 9f69a10 check: annotate owned domains in DoH-check output (non-block  ·  back to Domain Sniper  ·  watch-outbound: extend INTEL allowlist with RIRs, CZDS, 2026 0d22891 →