Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changeset/admin-brand-claim-verify-endpoint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

Add `POST /api/admin/organizations/:orgId/brand-claim/verify` for admin-triggered brand-claim sync. Wraps the existing `verifyDomainChallenge` service with an explicit orgId so it can be invoked with `ADMIN_API_KEY` to recover from missed `organization_domain.verified` webhooks (e.g. WorkOS dashboard manual flips that didn't propagate). When WorkOS already reports the domain as verified, the service short-circuits the DNS check and runs `applyVerifiedBrandClaim` directly.
49 changes: 49 additions & 0 deletions server/src/routes/admin/domains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
import { linkContactsByDomain } from "../../db/contacts-db.js";
import { resolveOrgsByDomains } from "../../db/domain-resolution-db.js";
import { complete, isLLMConfigured } from "../../utils/llm.js";
import { BrandDatabase } from "../../db/brand-db.js";
import { verifyDomainChallenge } from "../../services/brand-claim.js";

const slackDb = new SlackDatabase();
const logger = createLogger("admin-domains");
Expand Down Expand Up @@ -1159,6 +1161,53 @@ export function setupDomainRoutes(
}
);

// POST /api/admin/organizations/:orgId/brand-claim/verify
// Force-sync the brand registry from WorkOS for an org's domain. Wraps the
// same verifyDomainChallenge service the user-facing /api/me/member-profile
// route uses, but takes orgId explicitly so it can be invoked with the
// ADMIN_API_KEY for orgs the caller doesn't belong to.
//
// Recovery path for cases where WorkOS shows the domain as `verified` but
// the local `brands` row wasn't written — e.g. a missed
// organization_domain.verified webhook. The verify service short-circuits
// on isVerifiedState and runs applyVerifiedBrandClaim directly, so no DNS
// round-trip is needed.
apiRouter.post(
"/organizations/:orgId/brand-claim/verify",
requireAuth,
requireAdmin,
async (req, res) => {
const { orgId } = req.params;
const rawDomain = (req.body?.domain as string | undefined) ?? "";
const adoptPriorManifest = req.body?.adopt_prior_manifest === true;
if (!workos) {
return res.status(503).json({ error: "WorkOS not configured" });
}
if (!rawDomain) {
return res.status(400).json({ error: "domain is required" });
}
try {
const result = await verifyDomainChallenge({
workos,
brandDb: new BrandDatabase(),
orgId,
rawDomain,
adoptPriorManifest,
});
if (!result.ok) {
const status = result.code === "no_challenge" ? 404
: result.code === "still_pending" ? 400
: 500;
return res.status(status).json(result);
}
return res.json(result);
} catch (error) {
logger.error({ err: error, orgId, domain: rawDomain }, "Admin brand-claim verify failed");
return res.status(500).json({ error: "Internal server error" });
}
}
);

// POST /api/admin/organizations/:orgId/domains - Add a domain to an organization
// Writes to WorkOS first, then local DB is updated via webhook (or immediately for consistency)
apiRouter.post(
Expand Down
Loading