Use ProFix Directory in Discord

Three copy-paste recipes — inbound webhooks for scheduled posts, a discord.js bot with slash commands for ad-hoc lookups, and rich embed previews with the ProFix Verified badge.

TL;DR

  • Inbound webhook: cron a curl call against the ProFix permit leaderboard, post the result to a Discord channel webhook URL.
  • Bot slash commands: a 60-line discord.js bot registers /profix and calls /api/embed/{trade}-{city}.json.
  • Rich embed previews: a single fetch + JSON payload produces a link unfurl with badge thumbnail, trust tier, and permit count.
  • Permissions: Send Messages + Embed Links + Use Slash Commands. Nothing else.

Three ways to use ProFix data in Discord

Option A — Inbound webhook + scheduled jobs

The simplest path. Server admins can create a webhook in a channel's settings → Integrations → Webhooks. Copy the URL into a secret manager, then POST JSON to it from any cron job. Discord accepts richer payloads than Slack — embeds, thumbnails, colors, fields — so the same script can produce a much prettier post.

# Post the top 10 Cleveland HVAC contractors (by verified permits in the
# last 12 months) to a Discord channel. Works on a cron, GitHub Action, or
# Cloudflare scheduled worker.
WEBHOOK="https://discord.com/api/webhooks/000/XXXXXXXX"

curl -s "https://profixdirectory.com/api/permit-leaderboard.json?trade=hvac&county=cuyahoga&top=10" \
  | jq '{
      username: "ProFix Directory",
      avatar_url: "https://profixdirectory.com/icon.svg",
      content: "**Top Cleveland HVAC contractors (last 12 months of verified permits)**",
      embeds: [{
        title: "Permit leaderboard — HVAC × Cuyahoga County",
        url: "https://profixdirectory.com/permits-leaderboard",
        color: 3105218,
        description: (.leaderboards[0].entries
          | map("• [\(.pro.name)](https://profixdirectory.com/pro/\(.pro.slug)) — \(.permit_count) permits")
          | join("\n"))
      }]
    }' \
  | curl -s -X POST -H "Content-Type: application/json" --data-binary @- "$WEBHOOK"

Discord webhooks have no rate limit you'll hit at hourly cadence (30 requests/min per channel is the documented limit). The color field is a decimal RGB integer — 3105218 renders as ProFix accent green; #0a66c2 = 681666 for the primary blue. The brand-assets feed has the full palette.

Option B — Bot slash commands (discord.js)

For interactive lookups, register a slash command via discord.js. The snippet below registers /profix with two string options (trade + city), proxies the embed endpoint, and replies with up to five matches:

// discord.js v14 + Bun. Registers a /profix slash command and routes
// it to the public ProFix embed endpoint. Run with: bun run bot.ts
import { Client, GatewayIntentBits, REST, Routes, SlashCommandBuilder } from "discord.js";

const TOKEN = process.env.DISCORD_TOKEN!;
const APP_ID = process.env.DISCORD_APP_ID!;
const GUILD_ID = process.env.DISCORD_GUILD_ID!;

const command = new SlashCommandBuilder()
  .setName("profix")
  .setDescription("Find verified Ohio contractors via ProFix Directory")
  .addStringOption((opt) =>
    opt.setName("trade").setDescription("plumber, hvac, electrician, etc.").setRequired(true),
  )
  .addStringOption((opt) =>
    opt.setName("city").setDescription("toledo, cleveland, columbus, etc.").setRequired(true),
  );

await new REST({ version: "10" })
  .setToken(TOKEN)
  .put(Routes.applicationGuildCommands(APP_ID, GUILD_ID), { body: [command.toJSON()] });

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.on("interactionCreate", async (interaction) => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== "profix") return;

  const trade = interaction.options.getString("trade", true);
  const city = interaction.options.getString("city", true);
  await interaction.deferReply();

  const slug = `${trade}-${city}`;
  const data = await fetch(`https://profixdirectory.com/api/embed/${slug}.json`).then((r) =>
    r.ok ? r.json() : null,
  );

  if (!data || data.pros.length === 0) {
    await interaction.editReply(`No ProFix matches for \`${slug}\`. Try a different trade × city.`);
    return;
  }

  const lines = data.pros.slice(0, 5).map(
    (p: { name: string; phone: string; profile_url: string }) => `• [${p.name}](${p.profile_url}) — ${p.phone}`,
  );
  await interaction.editReply({
    embeds: [
      {
        title: `Top ${trade}s in ${city}`,
        url: `https://profixdirectory.com/${trade}-${city}`,
        description: lines.join("\n"),
        color: 3105218,
        footer: { text: "Data: ProFix Directory · CC-BY-4.0" },
      },
    ],
  });
});

await client.login(TOKEN);

Replace applicationGuildCommands with applicationCommands to register globally instead of in a single guild. Global commands take up to an hour to propagate; guild commands are instant — develop in guild scope, ship in global scope.

Option C — Rich embed previews

When someone links a pro in a community server, you can render a per-pro embed with the ProFix Verified badge as the thumbnail. The pro JSON dossier endpoint already returns everything the embed needs — name, location, trade list, verification tier, permit count:

// Post a rich embed for a single ProFix Verified contractor. Useful when
// a neighborhood server links a recommendation — the badge image, trust
// tier, and permit count all surface in the message preview.
const slug = "action-sewer-cleaning-plumbing-llc-toledo";
const pro = await fetch(`https://profixdirectory.com/api/pro/${slug}.json`).then((r) => r.json());

await fetch(process.env.DISCORD_WEBHOOK_URL!, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    username: "ProFix Directory",
    embeds: [
      {
        title: pro.name,
        url: `https://profixdirectory.com/pro/${slug}`,
        description: `${pro.city}, OH · ${pro.trades.join(", ")}`,
        thumbnail: { url: `https://profixdirectory.com/api/badge/${slug}.svg` },
        fields: [
          { name: "Trust tier", value: pro.verification_tier, inline: true },
          { name: "Permits (12mo)", value: String(pro.permit_count_12mo ?? 0), inline: true },
        ],
        footer: { text: "Verified by ProFix Directory · CC-BY-4.0" },
      },
    ],
  }),
});

Discord caches embed thumbnails for several hours — for tier transitions that need to be visible immediately, link the canonical /pro/{slug} profile page in the embed URL field instead of relying on the thumbnail.

When to use each option

The three options trade off effort vs interactivity. Option A (webhook + cron) is the right default for any server-wide digest — top contractors of the month, weekly newsroom recap, monthly coverage stats. The cost is one cron job; the result is a styled embed in the channel. Pick Option A when the post should land on a schedule.

Option B (discord.js bot) earns its keep on community servers where members want to type /profix plumber columbus and get an answer inline. Neighborhood Discords are a perfect fit — the moderator installs the bot once and any member can pull verified Ohio contractors without leaving Discord. The cost is a small always-on worker (or a free Repl / Railway / Fly.io tier) plus the developer-portal setup.

Option C (rich embed previews for a specific pro) is for the “someone shared a recommendation, let's show their badge automatically” use case. Wire it into your bot's messageCreate handler — if a message contains a profixdirectory.com/pro/{slug} URL, re-fetch the dossier and post the embed. Discord caches embeds for a few hours, so unfurls feel snappy even on cold posts.

Setup steps for a basic Discord application

  1. Go to discord.com/developers/applications and click New Application. Name it “ProFix” or per your community.
  2. Under Bot, click Add Bot and copy the token into a secret manager. Treat it like an API key — anyone with this token can impersonate the bot.
  3. Under OAuth2 → URL Generator, pick scopes bot + applications.commands, plus bot permissions Send Messages, Embed Links, Use Slash Commands. Copy the generated invite link and use it to add the bot to your server.
  4. For a single-server install, copy your server's ID (right-click the server → Copy ID with Developer Mode enabled) and use it as DISCORD_GUILD_ID in the snippet above.
  5. Run the bot. discord.js handles reconnect, presence, and command dispatch internally — no further plumbing required for the basic case.

Security caveats

Channel routing + community patterns

Neighborhood Discord servers tend to want different feeds in different channels — #contractors for the permit leaderboard, #news for the newsroom RSS, #urgent for any active outage status. The cleanest pattern is one webhook per channel, all driven from a single cron worker that loops over a small config map of { channel, feed, schedule }entries. The worker can also de-duplicate posts using Cloudflare KV or any small key-value store — useful when the newsroom RSS occasionally re-publishes an item. Discord bots can also pin the latest digest message so newcomers see it without scrolling — a manual one-liner that's worth the moderator's time on a high-traffic server.

Brand assets

The ProFix Verified badge SVG works as an embed thumbnail (it's pure inline SVG, no external assets). The brand-assets feed at /api/brand-assets.json ships the logo URLs, color palette (decimal RGB conversions are easy — just parseInt("0a66c2", 16)), and attribution text required for partner integrations. For per-pro badges, see /badges.

FAQ

Do I need to verify Discord interactions cryptographically?
If you use the discord.js gateway (websocket) bot pattern shown above, the library handles auth via the bot token. If you instead use HTTP interactions (a webhook URL Discord POSTs to), you MUST verify the ed25519 signature on every request — Discord ships a public key per app for exactly this. The discord-interactions npm package handles the verification in one call.
Does ProFix have an official Discord app in the App Directory?
Not yet. You install your own copy of the app to your server using the snippet above. We will publish an App-Directory-listed bot once enough community servers ask for it; email hello@profixdirectory.com to vote it up.
What permissions does the bot need?
Send Messages, Embed Links, and Use Slash Commands. Nothing else. ProFix never reads message history, member presence, or any user data from your server.
Can I rate-limit /profix per user?
Yes — discord.js exposes interaction.user.id and you can keep a per-user Map<string, number> of last-call timestamps. A 30-second floor is plenty given the ProFix endpoints are CDN-cached at 1 hour.
Will the ProFix Verified badge animate or auto-update inside Discord?
The badge SVG is static when Discord caches the embed thumbnail (typically a few hours). The underlying badge endpoint re-renders on every request as the pro's tier changes, so a fresh repost or cache bust will show the latest tier. For a live-updating display, prefer linking to /pro/{slug} directly.

Related

Ask your AI about this

Hand the question to your preferred assistant — it will use ProFix Directory's open MCP server and llms.txt as context.

Emergency