{
  "id": "webhook-listener",
  "name": "Webhook Listener",
  "category": "automation",
  "tags": ["webhook", "automation", "incoming", "trigger", "http", "callback"],
  "description": "Live feed of incoming webhook payloads. Polls /api/webhook/recent every 3s and renders the last 50 events with method, path, and body preview.",
  "triggers": ["webhook listener", "webhook feed", "incoming webhooks", "watch webhooks", "webhook viewer", "webhook log"],
  "defaultSize": { "w": 6, "h": 5 },
  "source": "function WebhookListener() {\n  const [events, setEvents] = React.useState([]);\n  const [error, setError] = React.useState('');\n  const [paused, setPaused] = React.useState(false);\n  const [path, setPath] = React.useState('REPLACE_WITH_PATH');\n  React.useEffect(() => {\n    if (paused) return;\n    let alive = true;\n    const tick = async () => {\n      try {\n        const url = path ? `/api/webhook/recent?path=${encodeURIComponent(path)}` : '/api/webhook/recent';\n        const r = await titan.api.call(url, { method: 'GET' });\n        if (!alive) return;\n        const list = Array.isArray(r) ? r : (r?.events || []);\n        setEvents(list.slice(0, 50));\n        setError('');\n      } catch (e) { setError(String(e?.message || e)); }\n    };\n    tick();\n    const id = setInterval(tick, 3000);\n    return () => { alive = false; clearInterval(id); };\n  }, [paused, path]);\n  return (\n    <div style={{padding:12,fontFamily:'system-ui',height:'100%',display:'flex',flexDirection:'column'}}>\n      <div style={{display:'flex',gap:8,alignItems:'center',marginBottom:10}}>\n        <input value={path} onChange={e=>setPath(e.target.value)} placeholder=\"Filter path (optional)\"\n          style={{flex:1,padding:'6px 10px',border:'1px solid #ccc',borderRadius:6,fontSize:13}}/>\n        <button onClick={()=>setPaused(!paused)} style={{padding:'6px 12px',borderRadius:6,border:'1px solid #ccc',background:paused?'#FF9500':'#34C759',color:'#fff',cursor:'pointer'}}>{paused?'Resume':'Pause'}</button>\n      </div>\n      {error && <div style={{color:'#FF3B30',fontSize:12,marginBottom:6}}>⚠ {error}</div>}\n      <div style={{flex:1,overflow:'auto',background:'#f9f9f9',borderRadius:8,padding:8}}>\n        {events.length === 0 ? <div style={{textAlign:'center',color:'#888',padding:30}}>No events yet — POST to /api/webhook to send one.</div> :\n          events.map((e,i)=>(\n            <div key={i} style={{padding:8,marginBottom:6,background:'#fff',borderRadius:6,boxShadow:'0 1px 2px rgba(0,0,0,0.05)'}}>\n              <div style={{display:'flex',gap:8,alignItems:'center',fontSize:12}}>\n                <span style={{padding:'2px 8px',borderRadius:4,background:'#007AFF',color:'#fff',fontWeight:600}}>{e.method||'POST'}</span>\n                <span style={{fontFamily:'monospace'}}>{e.path||'/'}</span>\n                <span style={{marginLeft:'auto',color:'#888'}}>{e.ts ? new Date(e.ts).toLocaleTimeString() : ''}</span>\n              </div>\n              <pre style={{margin:'6px 0 0',fontSize:11,color:'#333',whiteSpace:'pre-wrap',wordBreak:'break-word',maxHeight:100,overflow:'auto'}}>{typeof e.body==='string' ? e.body : JSON.stringify(e.body||{},null,2)}</pre>\n            </div>\n          ))\n        }\n      </div>\n    </div>\n  );\n}\nrender(<WebhookListener/>);",
  "placeholders": [
    { "name": "REPLACE_WITH_PATH", "description": "Optional webhook path filter (leave blank to show all)", "default": "" }
  ]
}
