Skip to content
Integrations
Integrations

GitHub

Trigger AgentField reasoners from signed GitHub webhooks.

GitHub connect dialog for a signed webhook trigger

The github source verifies GitHub's X-Hub-Signature-256 header with your webhook secret and dispatches the delivery to a reasoner.

Source Surface

SurfaceValue
Source namegithub
KindHTTP webhook
SecretRequired; GitHub webhook secret from secret_env
ConfigNo per-source config
Event typeX-GitHub-Event, plus .action when the payload has an action field
IdempotencyX-GitHub-Delivery
Reasoner inputThe GitHub JSON payload as event, with trigger metadata in _meta

GitHub does not ship a separate capability node in OSS. The integration surface is the signed webhook source.

Agent Code

from agentfield import Agent, on_event

app = Agent(node_id="repo-agent")

@app.reasoner()
@on_event(
    source="github",
    types=["pull_request.opened", "pull_request.synchronize", "issues.opened"],
    secret_env="GITHUB_WEBHOOK_SECRET",
)
async def handle_github(event: dict, trigger=None):
    return {
        "event_type": trigger.event_type if trigger else None,
        "delivery": trigger.idempotency_key if trigger else None,
        "action": event.get("action"),
    }

app.run()
import { Agent } from '@agentfield/sdk';

const agent = new Agent({ nodeId: 'repo-agent' });

// The github source verifies X-Hub-Signature-256, then dispatches the
// GitHub payload as ctx.input.event with trigger metadata in ctx.input._meta.
// The TypeScript SDK has no declarative source binding — connect the
// `github` source to this reasoner from the UI (or trigger REST API).
agent.reasoner('handleGithub', async (ctx) => {
  const { event, _meta } = ctx.input;
  return {
    event_type: _meta?.event_type ?? null,
    delivery: _meta?.idempotency_key ?? null,
    action: event?.action,
  };
});

agent.serve();
a, _ := agent.New(agent.Config{NodeID: "repo-agent", Version: "1.0.0"})

// Declare the github binding in code. The control plane verifies
// X-Hub-Signature-256 with GITHUB_WEBHOOK_SECRET, then dispatches the
// payload as input["event"] with metadata in input["_meta"]. The event name
// is combined with the payload action (e.g. pull_request.opened).
a.RegisterReasoner("handle_github", func(ctx context.Context, input map[string]any) (any, error) {
    event, _ := input["event"].(map[string]any)
    meta, _ := input["_meta"].(map[string]any)

    var eventType, delivery any
    if meta != nil {
        eventType = meta["event_type"]
        delivery = meta["idempotency_key"]
    }

    return map[string]any{
        "event_type": eventType,
        "delivery":   delivery,
        "action":     event["action"],
    }, nil
},
    agent.WithEventTrigger("github", "pull_request.opened", "pull_request.synchronize", "issues.opened"),
    agent.WithTriggerSecretEnv("GITHUB_WEBHOOK_SECRET"),
)

a.Serve(context.Background())

GitHub Setup

In the repository or organization settings, create a webhook that points at the AgentField trigger URL. Use application/json, set a webhook secret that matches GITHUB_WEBHOOK_SECRET, and choose the event families your reasoner handles.

Common event families:

  • pull_request
  • push
  • issues
  • issue_comment
  • workflow_run

When the payload includes an action, AgentField combines it with the GitHub event name. A pull_request webhook with {"action": "opened"} becomes pull_request.opened.

Dispatch Contract

At dispatch time, the target reasoner receives the normalized payload and metadata:

{
  "event": {
    "action": "opened",
    "pull_request": { "number": 42 },
    "repository": { "full_name": "Agent-Field/agentfield" }
  },
  "_meta": {
    "source": "github",
    "event_type": "pull_request.opened",
    "idempotency_key": "<X-GitHub-Delivery>"
  }
}
  • X-GitHub-Delivery becomes the idempotency key.
  • Signature verification happens before dispatch.
  • The GitHub payload is passed through as the normalized event body.