Integrations
Slack
Trigger AgentField reasoners from signed Slack Events API requests.
The slack source verifies Slack's request signature and routes workspace events to a reasoner.
Source Surface
| Surface | Value |
|---|---|
| Source name | slack |
| Kind | HTTP webhook |
| Secret | Required; Slack signing secret from secret_env |
| Config | Optional tolerance_seconds, default 300 |
| Event type | Envelope type, or inner event.type for event_callback |
| Idempotency | Slack event_id |
| Reasoner input | Slack envelope as event, with trigger metadata in _meta |
Slack does not ship a separate capability node in OSS. The integration surface is the signed Events API source.
Agent Code
from agentfield import Agent, on_event
app = Agent(node_id="slack-agent")
@app.reasoner()
@on_event(
source="slack",
types=["app_mention", "message"],
secret_env="SLACK_SIGNING_SECRET",
)
async def handle_slack(event: dict, trigger=None):
slack_event = event.get("event", event)
return {
"type": trigger.event_type if trigger else event.get("type"),
"text": slack_event.get("text"),
"channel": slack_event.get("channel"),
}
app.run()import { Agent } from '@agentfield/sdk';
const agent = new Agent({ nodeId: 'slack-agent' });
// The slack source verifies the signature, then dispatches the Slack
// envelope as ctx.input.event with trigger metadata in ctx.input._meta.
// The TypeScript SDK has no declarative source binding — connect the
// `slack` source to this reasoner from the UI (or trigger REST API).
agent.reasoner('handleSlack', async (ctx) => {
const { event, _meta } = ctx.input;
const slackEvent = event?.event ?? event;
return {
type: _meta?.event_type ?? event?.type,
text: slackEvent?.text,
channel: slackEvent?.channel,
};
});
agent.serve();a, _ := agent.New(agent.Config{NodeID: "slack-agent", Version: "1.0.0"})
// Declare the slack binding in code. The control plane verifies the Slack
// signature, then dispatches the envelope as input["event"] with metadata
// in input["_meta"]. event_callback payloads are unwrapped so types filter
// on the inner Slack event type.
a.RegisterReasoner("handle_slack", func(ctx context.Context, input map[string]any) (any, error) {
event, _ := input["event"].(map[string]any)
meta, _ := input["_meta"].(map[string]any)
slackEvent := event
if inner, ok := event["event"].(map[string]any); ok {
slackEvent = inner
}
var eventType any
if meta != nil {
eventType = meta["event_type"]
} else {
eventType = event["type"]
}
return map[string]any{
"type": eventType,
"text": slackEvent["text"],
"channel": slackEvent["channel"],
}, nil
},
agent.WithEventTrigger("slack", "app_mention", "message"),
agent.WithTriggerSecretEnv("SLACK_SIGNING_SECRET"),
)
a.Serve(context.Background())Slack Setup
Create a Slack app, enable Event Subscriptions, and use the AgentField trigger URL as the request URL. Set the control-plane env var named by secret_env to the app's signing secret.
Useful Slack events:
app_mentionmessage.channelsmessage.imreaction_added
Dispatch Contract
For event_callback, AgentField filters on the inner event type but keeps the envelope available:
{
"event": {
"type": "event_callback",
"event_id": "Ev123",
"event": {
"type": "app_mention",
"text": "<@bot> triage this",
"channel": "C123"
}
},
"_meta": {
"source": "slack",
"event_type": "app_mention",
"idempotency_key": "Ev123"
}
}- Slack signatures use
v0:<timestamp>:<raw_body>. - AgentField rejects requests outside the configured timestamp tolerance.
event_callbackpayloads are unwrapped so reasoners can filter on the inner event type.