diff --git a/web/src/components/app/automations/AutomationFlow.tsx b/web/src/components/app/automations/AutomationFlow.tsx index df35db41..dc9cd7a6 100644 --- a/web/src/components/app/automations/AutomationFlow.tsx +++ b/web/src/components/app/automations/AutomationFlow.tsx @@ -38,6 +38,7 @@ import { CheckCircle2Icon, CheckIcon, CheckSquareIcon, + CopyIcon, GitBranchIcon, GlobeIcon, HistoryIcon, @@ -101,8 +102,10 @@ import { isNativeAction, nativeActionNeeds, triggerCarriesThread, + triggerIsInboundWebhook, type TriggerFieldDef, } from "@/lib/api/models/app/automations/meta"; +import { API_URL } from "@/lib/information"; import CategoryPicker from "@/components/app/contacts/CategoryPicker"; import { ExpressionReference } from "@/components/app/automations/ExpressionReference"; import DealStagePicker from "@/components/app/crm/DealStagePicker"; @@ -1083,6 +1086,7 @@ export default function AutomationFlow({ setTrigger(ev); updateNodeData("trigger", { label: triggerLabel(ev) }); }} + inboundUrl={automation.inbound_url} // condition onCondition={(cond) => updateNodeData(selectedNode.id, { condition: cond, label: conditionLabel(cond) })} // action @@ -1257,6 +1261,56 @@ function InsightsPanel({ ); } +// InboundUrlField shows the automation's unique inbound-webhook URL (read-only) +// with a copy button. The stored value is a path; we prefix the API origin so +// the copied value is the full URL an external system POSTs to. +function InboundUrlField({ inboundUrl }: { inboundUrl?: string }) { + const [copied, setCopied] = React.useState(false); + if (!inboundUrl) { + return ( +

+ Save this automation to generate its unique webhook URL. An external system POSTs JSON to that URL and + the body becomes the event payload (reference any field with {"{{.field}}"}). +

+ ); + } + const full = `${API_URL}${inboundUrl}`; + const copy = async () => { + try { + await navigator.clipboard.writeText(full); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } catch { + /* clipboard blocked — the value is still selectable in the field */ + } + }; + return ( +
+ +
+ e.currentTarget.select()} + className="flex-1 h-7 rounded-md border border-slate-200 bg-slate-50 px-2 text-[11.5px] font-mono text-slate-700 focus:border-sky-400 focus:ring-2 focus:ring-sky-100 focus:outline-none" + /> + +
+

+ POST JSON here to run this automation. The body becomes the event payload, so reference its keys with{" "} + {"{{.field}}"} in actions and conditions. +

+
+ ); +} + // ── Editor panel ───────────────────────────────────────────────────────────── function NodeEditor({ node, @@ -1264,6 +1318,7 @@ function NodeEditor({ selfId, trigger, onTrigger, + inboundUrl, onCondition, targets, connLabel, @@ -1276,6 +1331,7 @@ function NodeEditor({ selfId: string; trigger: string; onTrigger: (ev: string) => void; + inboundUrl?: string; onCondition: (c: AutomationCondition) => void; targets: IntegrationConnection[]; connLabel: (id?: string) => string; @@ -1316,9 +1372,13 @@ function NodeEditor({ -

- Add a condition (IF) below the trigger to branch on the event — e.g. only positive replies, or by source. -

+ {triggerIsInboundWebhook(trigger) ? ( + + ) : ( +

+ Add a condition (IF) below the trigger to branch on the event — e.g. only positive replies, or by source. +

+ )} ) : isCondition ? ( = { "slack.notify": "Send a Slack message", @@ -302,6 +310,9 @@ export const TRIGGER_VARIABLES: Record = { "campaign.unsubscribed": ["contact_email", "contact_id", "campaign_id", "source"], "warmup.health_changed": ["email", "new_state", "previous_state", "reason"], "campaign.action": ["contact_email", "contact_id", "campaign_id", "campaign_name", "first_name", "last_name", "company", "phone"], + // Inbound webhook payloads are caller-defined, so there are no fixed + // variables to offer; the user references their own JSON keys directly. + "inbound.webhook": [], }; export function triggerVariables(triggerEvent: string): string[] { diff --git a/web/src/lib/api/models/app/integrations/Integration.ts b/web/src/lib/api/models/app/integrations/Integration.ts index 74ccd09a..399d28d2 100644 --- a/web/src/lib/api/models/app/integrations/Integration.ts +++ b/web/src/lib/api/models/app/integrations/Integration.ts @@ -305,6 +305,7 @@ export const EVENT_LABELS: Record = { "meeting.rescheduled": "Meeting rescheduled", "meeting.canceled": "Meeting canceled", "campaign.action": "Launched by a campaign step", + "inbound.webhook": "Inbound webhook", }; // Which action a provider performs for an event subscription.