From 46226f7d8162dcebd371ead4aa720b5ef4b31b28 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Wed, 3 Sep 2025 12:52:42 -0700 Subject: [PATCH 1/3] Update Slack webhooks to support latest events --- apps/web/lib/integrations/slack/transform.ts | 173 ++++++++++++++---- .../lib/integrations/slack/ui/settings.tsx | 4 + 2 files changed, 137 insertions(+), 40 deletions(-) diff --git a/apps/web/lib/integrations/slack/transform.ts b/apps/web/lib/integrations/slack/transform.ts index 53ae0f12faf..0a66ac3313e 100644 --- a/apps/web/lib/integrations/slack/transform.ts +++ b/apps/web/lib/integrations/slack/transform.ts @@ -4,6 +4,7 @@ import { z } from "zod"; import { WebhookTrigger } from "../../types"; import { webhookPayloadSchema } from "../../webhook/schemas"; import { + BountyEventWebhookPayload, ClickEventWebhookPayload, CommissionEventWebhookPayload, LeadEventWebhookPayload, @@ -11,7 +12,7 @@ import { SaleEventWebhookPayload, } from "../../webhook/types"; -const createLinkTemplate = ({ +const linkTemplates = ({ data, event, }: { @@ -50,7 +51,7 @@ const createLinkTemplate = ({ }; }; -const clickLinkTemplate = ({ data }: { data: ClickEventWebhookPayload }) => { +const linkClickedTemplate = ({ data }: { data: ClickEventWebhookPayload }) => { const { link, click } = data; const linkToClicks = `${APP_DOMAIN}/events?event=clicks&domain=${link.domain}&key=${link.key}`; @@ -102,7 +103,7 @@ const clickLinkTemplate = ({ data }: { data: ClickEventWebhookPayload }) => { }; }; -const createLeadTemplate = ({ data }: { data: LeadEventWebhookPayload }) => { +const leadCreatedTemplate = ({ data }: { data: LeadEventWebhookPayload }) => { const { customer, click, link } = data; const linkToLeads = `${APP_DOMAIN}/events?event=leads&domain=${link.domain}&key=${link.key}`; @@ -151,7 +152,7 @@ const createLeadTemplate = ({ data }: { data: LeadEventWebhookPayload }) => { }; }; -const createSaleTemplate = ({ data }: { data: SaleEventWebhookPayload }) => { +const saleCreatedTemplate = ({ data }: { data: SaleEventWebhookPayload }) => { const { customer, click, sale, link } = data; const amountInDollars = (sale.amount / 100).toFixed(2); const linkToSales = `${APP_DOMAIN}/events?event=sales&domain=${link.domain}&key=${link.key}`; @@ -201,7 +202,7 @@ const createSaleTemplate = ({ data }: { data: SaleEventWebhookPayload }) => { }; }; -const enrolledPartnerTemplate = ({ +const partnerEnrolledTemplate = ({ data, }: { data: PartnerEventWebhookPayload; @@ -238,18 +239,16 @@ const enrolledPartnerTemplate = ({ }; }; -// TODO (kiran): -// We should improve this template const commissionCreatedTemplate = ({ data, }: { data: CommissionEventWebhookPayload; }) => { - const { id, amount, earnings } = data; + const { id, amount, earnings, currency, partner, customer } = data; - const formattedAmount = currencyFormatter(amount / 100); - - const formattedEarnings = currencyFormatter(earnings / 100); + const formattedAmount = currencyFormatter(amount / 100, { currency }); + const formattedEarnings = currencyFormatter(earnings / 100, { currency }); + const linkToCommissions = `${APP_DOMAIN}/partners/commissions`; return { blocks: [ @@ -265,15 +264,50 @@ const commissionCreatedTemplate = ({ fields: [ { type: "mrkdwn", - text: `*Commission ID*\n${id}`, + text: `*Partner*\n${partner.name}`, }, { type: "mrkdwn", - text: `*Amount*\n${formattedAmount}`, + text: `*Email*\n`, + }, + ], + }, + { + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*Commission Amount*\n${formattedAmount}`, }, { type: "mrkdwn", - text: `*Earnings*\n${formattedEarnings}`, + text: `*Partner Earnings*\n${formattedEarnings}`, + }, + ], + }, + ...(customer + ? [ + { + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*Customer*\n${customer.name}`, + }, + { + type: "mrkdwn", + text: `*Customer Email*\n`, + }, + ], + }, + ] + : []), + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: `Commission ID: ${id} | <${linkToCommissions}|View on Dub>`, }, ], }, @@ -281,36 +315,93 @@ const commissionCreatedTemplate = ({ }; }; -// Minimal bounty templates (safe defaults) -const bountyCreatedTemplate = ({} /* data */ : { data: unknown }) => ({ - blocks: [ - { - type: "section", - text: { type: "mrkdwn", text: "*New bounty created* :money_with_wings:" }, - }, - ], -}); +const bountyTemplates = ({ + data, + event, +}: { + data: BountyEventWebhookPayload; + event: WebhookTrigger; +}) => { + const { id, name, description, rewardAmount, type, startsAt, endsAt } = data; + + const eventMessages = { + "bounty.created": "*New bounty created* :money_with_wings:", + "bounty.updated": "*Bounty updated* :memo:", + }; + + const formattedReward = currencyFormatter(rewardAmount / 100); + const linkToBounty = `${APP_DOMAIN}/bounties/${id}`; -const bountyUpdatedTemplate = ({} /* data */ : { data: unknown }) => ({ - blocks: [ - { - type: "section", - text: { type: "mrkdwn", text: "*Bounty updated* :memo:" }, - }, - ], -}); + return { + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: eventMessages[event], + }, + }, + { + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*Bounty Name*\n${name || "Untitled Bounty"}`, + }, + { + type: "mrkdwn", + text: `*Reward Amount*\n${formattedReward}`, + }, + ], + }, + { + type: "section", + fields: [ + { + type: "mrkdwn", + text: `*Type*\n${type.charAt(0).toUpperCase() + type.slice(1)}`, + }, + { + type: "mrkdwn", + text: `*Duration*\n${new Date(startsAt).toLocaleDateString()}${endsAt ? ` - ${new Date(endsAt).toLocaleDateString()}` : " (No end date)"}`, + }, + ], + }, + ...(description + ? [ + { + type: "section", + text: { + type: "mrkdwn", + text: `*Description*\n${description}`, + }, + }, + ] + : []), + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: `<${linkToBounty}|View bounty on Dub>`, + }, + ], + }, + ], + }; +}; const slackTemplates: Record = { - "link.created": createLinkTemplate, - "link.updated": createLinkTemplate, - "link.deleted": createLinkTemplate, - "link.clicked": clickLinkTemplate, - "lead.created": createLeadTemplate, - "sale.created": createSaleTemplate, - "partner.enrolled": enrolledPartnerTemplate, + "link.created": linkTemplates, + "link.updated": linkTemplates, + "link.deleted": linkTemplates, + "link.clicked": linkClickedTemplate, + "lead.created": leadCreatedTemplate, + "sale.created": saleCreatedTemplate, + "partner.enrolled": partnerEnrolledTemplate, "commission.created": commissionCreatedTemplate, - "bounty.created": bountyCreatedTemplate, - "bounty.updated": bountyUpdatedTemplate, + "bounty.created": bountyTemplates, + "bounty.updated": bountyTemplates, }; export const formatEventForSlack = ( @@ -326,9 +417,11 @@ export const formatEventForSlack = ( const isLinkEvent = ["link.created", "link.updated", "link.deleted"].includes( event, ); + const isBountyEvent = ["bounty.created", "bounty.updated"].includes(event); return template({ data, ...(isLinkEvent && { event }), + ...(isBountyEvent && { event }), }); }; diff --git a/apps/web/lib/integrations/slack/ui/settings.tsx b/apps/web/lib/integrations/slack/ui/settings.tsx index 67d2ed5a4c1..b62f88cb8fa 100644 --- a/apps/web/lib/integrations/slack/ui/settings.tsx +++ b/apps/web/lib/integrations/slack/ui/settings.tsx @@ -18,6 +18,10 @@ export const SlackSettings = (props: InstalledIntegrationInfoProps) => { "link.clicked", "lead.created", "sale.created", + "partner.enrolled", + "commission.created", + "bounty.created", + "bounty.updated", ]} /> )} From b01965d13131cd3d115a6bf62bb51b5024806932 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Wed, 3 Sep 2025 15:05:18 -0700 Subject: [PATCH 2/3] add axiom to singular webhook, fix description --- apps/web/app/(ee)/api/singular/webhook/route.ts | 7 ++++--- apps/web/lib/integrations/slack/transform.ts | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/web/app/(ee)/api/singular/webhook/route.ts b/apps/web/app/(ee)/api/singular/webhook/route.ts index f7b6af4db9e..8fc64e4f042 100644 --- a/apps/web/app/(ee)/api/singular/webhook/route.ts +++ b/apps/web/app/(ee)/api/singular/webhook/route.ts @@ -4,6 +4,7 @@ import { trackSingularLeadEvent } from "@/lib/integrations/singular/track-lead"; import { trackSingularSaleEvent } from "@/lib/integrations/singular/track-sale"; import { prisma } from "@dub/prisma"; import { getSearchParams } from "@dub/utils"; +import { AxiomRequest, withAxiom } from "next-axiom"; import { NextResponse } from "next/server"; import { z } from "zod"; @@ -36,7 +37,7 @@ const authSchema = z.object({ const singularWebhookToken = process.env.SINGULAR_WEBHOOK_TOKEN; // GET /api/singular/webhook – listen to Postback events from Singular -export const GET = async (req: Request) => { +export const GET = withAxiom(async (req: AxiomRequest) => { try { if (!singularWebhookToken) { throw new DubApiError({ @@ -114,8 +115,8 @@ export const GET = async (req: Request) => { } catch (error) { return handleAndReturnErrorResponse(error); } -}; +}); -export const HEAD = async () => { +export const HEAD = () => { return new Response("OK"); }; diff --git a/apps/web/lib/integrations/slack/transform.ts b/apps/web/lib/integrations/slack/transform.ts index 0a66ac3313e..6d97f61b56f 100644 --- a/apps/web/lib/integrations/slack/transform.ts +++ b/apps/web/lib/integrations/slack/transform.ts @@ -1,4 +1,4 @@ -import { APP_DOMAIN, currencyFormatter } from "@dub/utils"; +import { APP_DOMAIN, currencyFormatter, truncate } from "@dub/utils"; import { LinkWebhookEvent } from "dub/models/components"; import { z } from "zod"; import { WebhookTrigger } from "../../types"; @@ -346,7 +346,7 @@ const bountyTemplates = ({ fields: [ { type: "mrkdwn", - text: `*Bounty Name*\n${name || "Untitled Bounty"}`, + text: `*Bounty Name*\n${truncate(name, 140) || "Untitled Bounty"}`, }, { type: "mrkdwn", @@ -373,7 +373,7 @@ const bountyTemplates = ({ type: "section", text: { type: "mrkdwn", - text: `*Description*\n${description}`, + text: `*Description*\n${truncate(description, 140)}`, }, }, ] From 2289067c692610bad433f51db0f5aa2c23b4f35c Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Wed, 3 Sep 2025 15:30:51 -0700 Subject: [PATCH 3/3] final changes --- apps/web/lib/integrations/slack/transform.ts | 21 ++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/web/lib/integrations/slack/transform.ts b/apps/web/lib/integrations/slack/transform.ts index 6d97f61b56f..cafb2adb077 100644 --- a/apps/web/lib/integrations/slack/transform.ts +++ b/apps/web/lib/integrations/slack/transform.ts @@ -207,7 +207,8 @@ const partnerEnrolledTemplate = ({ }: { data: PartnerEventWebhookPayload; }) => { - const { name, email, country } = data; + const { name, email, country, partnerId } = data; + const linkToPartner = `${APP_DOMAIN}/program/partners?partnerId=${partnerId}`; return { blocks: [ @@ -235,6 +236,15 @@ const partnerEnrolledTemplate = ({ }, ], }, + { + type: "context", + elements: [ + { + type: "mrkdwn", + text: `Partner ID: ${partnerId} | <${linkToPartner}|View on Dub>`, + }, + ], + }, ], }; }; @@ -248,7 +258,7 @@ const commissionCreatedTemplate = ({ const formattedAmount = currencyFormatter(amount / 100, { currency }); const formattedEarnings = currencyFormatter(earnings / 100, { currency }); - const linkToCommissions = `${APP_DOMAIN}/partners/commissions`; + const linkToCommissions = `${APP_DOMAIN}/program/commissions`; return { blocks: [ @@ -330,7 +340,7 @@ const bountyTemplates = ({ }; const formattedReward = currencyFormatter(rewardAmount / 100); - const linkToBounty = `${APP_DOMAIN}/bounties/${id}`; + const linkToBounty = `${APP_DOMAIN}/program/bounties/${id}`; return { blocks: [ @@ -383,7 +393,7 @@ const bountyTemplates = ({ elements: [ { type: "mrkdwn", - text: `<${linkToBounty}|View bounty on Dub>`, + text: `<${linkToBounty}|View on Dub>`, }, ], }, @@ -421,7 +431,6 @@ export const formatEventForSlack = ( return template({ data, - ...(isLinkEvent && { event }), - ...(isBountyEvent && { event }), + ...((isLinkEvent || isBountyEvent) && { event }), }); };