OpenClaw ships with a tiny HTTP server that can accept any JSON a platform throws at it. GitHub throws a lot. This article walks through wiring GitHub webhooks straight into an OpenClaw agent, parsing the payload, and triggering useful automations—no polling, no bash glue, no third-party SaaS in the middle.

Why GitHub → OpenClaw beats cron polling

Polling a repository every few minutes adds latency, costs API quota, and sooner or later trips the X-RateLimit wire. Webhooks, by contrast, push the event the moment it happens. OpenClaw already keeps a Node.js process alive (the daemon), so adding one more route costs basically nothing. You get real-time responses: welcome a newcomer the second their PR lands, add labels before a maintainer even reads the issue, publish release notes while the tag is still warm.

Prerequisites and quick install

Versions that work

  • Node.js 22 or later (OpenClaw refuses to boot on 21.x and below.)
  • OpenClaw ≥ 0.34.0 (the release that introduced agent.webhook.on())
  • A repository where you can create webhooks (owner or maintainer rights)

Local vs ClawCloud

  • Local — perfect for hacking: npm create openclaw, then expose to the internet with ngrok http 3000 or cloudflared tunnel.
  • ClawCloud — skips the tunneling. Create an agent, grab the permanent HTTPS endpoint from the "Webhook" tab. Done.

Fresh install in 90 seconds

# Node 22 via volta or asdf — your pick volta install node@22 # Create an OpenClaw workspace npm create openclaw@latest my-bot cd my-bot && npm install # Start the daemon (runs gateway + webhook server) npm run dev

The dev script launches the gateway on http://localhost:3000, mounts the webhook surface at POST /api/webhook, and logs every incoming payload to stdout.

Expose the OpenClaw webhook endpoint

Option 1: ngrok (free but rotates URL)

ngrok http 3000 # outputs https://random.ngrok-free.app

Copy the HTTPS forwarding URL. Append /api/webhook; that full path is what GitHub will call.

Option 2: tunnel via Cloudflare (stable subdomain)

cloudflared tunnel create openclaw cloudflared tunnel route dns openclaw bot.example.com cloudflared tunnel run openclaw --url http://localhost:3000

The broker gives you https://bot.example.com/api/webhook. No monthly fees, stays up until you kill the tunnel.

Option 3: ClawCloud (already public)

In the ClawCloud dashboard: Agents → your-agent → Webhook. You get something like https://g1.claw.run/agent/abcd1234/webhook. There is nothing to expose; skip to the next section.

Register the GitHub webhook

Create the hook

  1. Open the repository → Settings → Webhooks → Add webhook.
  2. Payload URL: paste the endpoint you produced above.
  3. Content type: application/json
  4. Secret: generate a strong token, e.g. pwgen 32 1. We’ll store it in OpenClaw env vars.
  5. Select Let me select individual events and tick:
    • Push
    • Pull request
    • Issues
    • Release
  6. Save.

GitHub fires a ping event instantly. If OpenClaw logs it, the pipe is open.

Verify signature (recommended)

GitHub includes an X-Hub-Signature-256 header. The value is sha256=…. Add the secret to .env:

GITHUB_WEBHOOK_SECRET='u07Nd3lt7e9gA4tG4nZkP1Fp7vt3TlmG'

Inside your bot code:

import crypto from 'node:crypto'; agent.webhook.on('raw', async (req, body) => { const sig = req.headers['x-hub-signature-256']; if (!sig) throw new Error('Missing signature'); const hmac = crypto.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET) .update(body) .digest('hex'); if (sig !== `sha256=${hmac}`) throw new Error('Invalid signature'); });

Fail fast; don’t process forged payloads.

Parse the payload inside OpenClaw

OpenClaw exposes a thin event emitter:

agent.webhook.on(eventType, handler)

The eventType is the raw GitHub header X-GitHub-Event. You can use * to catch them all, but explicit is cleaner. Example:

agent.webhook.on('issues', async ({ action, issue, repository }) => { if (action !== 'opened') return; await agent.memory.put(`issue-${issue.number}-opened-at`, new Date().toISOString()); // trigger tools, send Slack alert, etc. });

You get the deserialized JSON plus the request headers. No need to JSON.parse().

Three real automations you can copy-paste

1. Auto-label new issues by keyword

Users rarely read your CONTRIBUTING guide. Let OpenClaw tidy the board:

import { Octokit } from 'octokit'; const gh = new Octokit({ auth: process.env.GITHUB_PAT }); const LABEL_MAP = { bug: [/error|stack trace|crash/i], docs: [/typo|documentation|readme/i], build: [/webpack|vite|rollup|eslint/i] }; agent.webhook.on('issues', async ({ action, issue, repository }) => { if (action !== 'opened') return; const labels = Object.entries(LABEL_MAP) .filter(([, regexArr]) => regexArr.some(r => r.test(issue.body))) .map(([label]) => label); if (labels.length === 0) return; await gh.rest.issues.addLabels({ owner: repository.owner.login, repo: repository.name, issue_number: issue.number, labels }); agent.log.info(`Labeled #${issue.number} with ${labels.join(', ')}`); });

Tip from the community: cache the regex compilation if you run thousands of repos on one agent.

2. Welcome first-time contributors on PR open

The GitHub UI marks first-time contributor, but a friendly comment beats a gray badge.

agent.webhook.on('pull_request', async ({ action, pull_request: pr, repository }) => { if (action !== 'opened') return; if (!pr.author_association || pr.author_association !== 'FIRST_TIME_CONTRIBUTOR') return; const msg = '👋 Thanks for your first PR! The CI will run in ~10 min. '\ + 'A maintainer will review once green. Meanwhile, please ensure the PR '\ + 'description answers the template questions. Happy hacking!'; await gh.rest.issues.createComment({ owner: repository.owner.login, repo: repository.name, issue_number: pr.number, body: msg }); });

You can replace the comment with a Slack or Discord ping by swapping gh.rest.issues.createComment for the Composio Slack tool.

3. Generate release notes when a new tag is pushed

Drafting release notes is busywork. Use OpenClaw’s LLM tooling to summarise merged PRs.

import { Configuration, OpenAI } from 'openai'; const openai = new OpenAI(new Configuration({ apiKey: process.env.OPENAI_KEY })); agent.webhook.on('release', async ({ action, release, repository }) => { if (action !== 'published') return; // we only care once the tag is live // Collect merged PRs since the previous release. const since = agent.memory.get('last-release-date') || '1970-01-01T00:00:00Z'; const { data: prs } = await gh.rest.pulls.list({ owner: repository.owner.login, repo: repository.name, state: 'closed', sort: 'updated', direction: 'desc', per_page: 100 }); const merged = prs.filter(pr => pr.merged_at && pr.merged_at > since); const bullets = merged.map(pr => `* ${pr.title} (#${pr.number})`).join('\n'); const prompt = `Summarise the following changes in 8-12 bullet points:\n${bullets}`; const ai = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: prompt }] }); const notes = ai.choices[0].message.content; await gh.rest.repos.updateRelease({ owner: repository.owner.login, repo: repository.name, release_id: release.id, body: notes }); agent.memory.put('last-release-date', release.published_at); agent.log.info(`Updated release notes for ${release.tag_name}`); });

This snippet uses agent.memory for state. On ClawCloud that memory is persisted in Postgres; locally it’s a JSON file under .claw/data.

Debugging and next steps

Watch the request log

TAIL_LOG=true npm run dev

The TAIL_LOG flag adds colored dumps of every incoming header and the first 2 kB of body. Helpful when a payload silently 500s.

Replay events

GitHub stores delivery history. In the Webhooks UI click a request, then Redeliver. No need to push dummy commits.

Common pitfalls

  • Endpoint returns 200 but nothing happens → forgot to await inside the handler, the function exited.
  • Multiple webhooks configured and getting duplicates → GitHub sends one per secret; prune old hooks.
  • Ngrok URL changed and GitHub keeps failing → update the Payload URL or pay for a fixed ngrok subdomain.
  • Octokit 3.x vs 4.x: methods moved under gh.rest.*. Make sure your code matches the version.

Where to go from here

  • Chain events into multi-step workflows with OpenClaw’s agent.sequence().
  • Persist context: store issue history in agent.memory and let the LLM answer duplicate questions.
  • Secure endpoints: behind Cloudflare Auth or use HMAC signature check shown earlier.
  • Share your recipes: github.com/openclaw/recipes is the community dump of battle-tested handlers.

Webhooks turn GitHub into an event stream; OpenClaw turns events into actions. You now have everything you need to teach your agent some manners, save yourself review time, and keep your repo tidy without lifting a finger.