/** * Integrations tab — inbound webhooks from AgentKit products (AgentGate, FormBridge). * Each integration posts HMAC-signed events to /api/events/ingest; configure the * shared secret here (stored encrypted at rest, never returned). */ import React, { useState } from 'react'; import { getConfig, updateConfig } from '../../api/client'; import { useApi } from '../../hooks/useApi'; const WEBHOOK_URL = `${window.location.origin}/api/events/ingest`; interface CardProps { title: string; description: string; /** Product name shown in the setup steps, e.g. "AgentGate". */ provider: string; /** Whether a secret is already configured (secrets are write-only). */ secretSet: boolean; loading: boolean; /** Webhook event names to subscribe to in the source product. */ events: string[]; onSaveSecret: (secret: string) => Promise; onSaved: () => void; } function WebhookIntegrationCard({ title, description, provider, secretSet, loading, events, onSaveSecret, onSaved, }: CardProps): React.ReactElement { const [secretForm, setSecretForm] = useState(''); const [saving, setSaving] = useState(false); const [saveSuccess, setSaveSuccess] = useState(false); const [copied, setCopied] = useState(false); const handleSaveSecret = async () => { if (!secretForm.trim()) return; setSaving(true); setSaveSuccess(false); try { await onSaveSecret(secretForm.trim()); setSaveSuccess(true); setSecretForm(''); onSaved(); } catch { // surfaced via the config error state } finally { setSaving(false); } }; const handleCopyUrl = async () => { await navigator.clipboard.writeText(WEBHOOK_URL); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (

{title}

{description}

{/* Webhook URL (read-only) */}

Webhook URL

Configure this URL in {provider}'s webhook settings

{WEBHOOK_URL}
{/* Webhook Secret status */}

Webhook Secret

HMAC-SHA256 shared secret (encrypted at rest, never shown)

{loading ? ( Loading… ) : secretSet ? ( Configured ✓ ) : ( Not set )}
{/* Update Secret */}
setSecretForm(e.target.value)} placeholder={secretSet ? 'Enter a new secret to replace' : 'Enter the shared secret'} className="flex-1 rounded border border-gray-300 px-3 py-1.5 text-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" />
{saveSuccess &&

✓ Secret saved

}
{/* Setup steps */}
  1. Copy the webhook URL above
  2. In {provider}, add a webhook with that URL and a shared secret
  3. Subscribe to events:{' '} {events.map((e, i) => ( {i > 0 && ', '} {e} ))}
  4. Save the same secret in the field above
); } export function IntegrationsTab(): React.ReactElement { const { data: configData, loading, error, refetch } = useApi(() => getConfig(), []); return (
{error && (
{error}
)} updateConfig({ agentGateSecret: secret })} onSaved={refetch} /> updateConfig({ formBridgeSecret: secret })} onSaved={refetch} />
); }