Octohook
Typed interactions with incoming Github webhooks
Install / Use
/learn @doodla/OctohookREADME
Octohook
Octohook parses GitHub webhook payloads into typed Python classes and provides a decorator-based system for routing webhooks to handlers.
Installation
pip install octohook
Quick Start
Define a handler for pull request events:
# handlers.py
from octohook import hook, WebhookEvent, WebhookEventAction
from octohook.events import PullRequestEvent
@hook(WebhookEvent.PULL_REQUEST, [WebhookEventAction.OPENED])
def on_pr_opened(event: PullRequestEvent):
print(f"PR opened: {event.pull_request.title}")
Wire it up in your web framework (example using Flask):
# app.py
from flask import Flask, request, Response
import octohook
app = Flask(__name__)
octohook.setup(modules=["handlers"])
@app.route('/webhook', methods=['POST'])
def webhook():
github_event = request.headers.get('X-GitHub-Event')
octohook.handle_webhook(event_name=github_event, payload=request.json)
return Response("OK", status=200)
Usage
Manual Parsing
Use octohook.parse() when you want direct control over webhook handling:
from flask import Flask, request, Response
import octohook
from octohook.events import PullRequestEvent
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
github_event = request.headers.get('X-GitHub-Event')
event = octohook.parse(github_event, request.json)
if isinstance(event, PullRequestEvent):
return Response(event.pull_request.title, status=200)
return Response("OK", status=200)
Decorator Routing
Use @hook when you have multiple handlers or want cleaner routing. The decorator takes four parameters:
event: AWebhookEventenum value (required)actions: List ofWebhookEventActionvalues (optional - omit to match any action)repositories: List of repository full names to filter on (optional)debug: WhenTrue, only debug hooks fire for that event (default:False)
from octohook import hook, WebhookEvent, WebhookEventAction
from octohook.events import PullRequestEvent
@hook(WebhookEvent.PULL_REQUEST, [WebhookEventAction.OPENED, WebhookEventAction.EDITED])
def on_pr_change(event: PullRequestEvent):
print(event.pull_request.title)
on_pr_change() is called with the parsed PullRequestEvent whenever a pull_request webhook arrives with an opened or edited action.
If you omit the actions parameter, the handler fires for any action. For events like push that have no action field, always omit actions.
Handler Discovery
Use setup() to load handlers from your modules:
import octohook
# Recursively imports handlers from the specified modules
octohook.setup(modules=["hooks", "webhooks.github"])
Repository Filtering
Filter hooks to specific repositories using their full name (e.g., "owner/repo"):
from octohook import hook, WebhookEvent
from octohook.events import PushEvent
@hook(WebhookEvent.PUSH, repositories=["myorg/backend", "myorg/frontend"])
def on_push(event: PushEvent):
print(f"Push to {event.repository.full_name}")
Debug Mode
Set debug=True on any hook to make only debug hooks fire for that event type:
from octohook import hook, WebhookEvent
from octohook.events import PullRequestEvent
@hook(WebhookEvent.PULL_REQUEST, debug=True)
def debug_pr(event: PullRequestEvent):
print(event) # Only this runs for PR events when debug=True
Complete Example
# hooks/github.py
from octohook import hook, WebhookEvent, WebhookEventAction
from octohook.events import LabelEvent, PullRequestEvent
@hook(WebhookEvent.LABEL, [WebhookEventAction.CREATED])
def on_label_created(event: LabelEvent):
print(f"Label created: {event.label.name}")
@hook(WebhookEvent.PULL_REQUEST)
def on_any_pr_event(event: PullRequestEvent):
print(f"PR #{event.number}: {event.action}")
# app.py
from flask import Flask, request, Response
import octohook
app = Flask(__name__)
octohook.setup(modules=["hooks"])
@app.route('/webhook', methods=['POST'])
def webhook():
github_event = request.headers.get('X-GitHub-Event')
octohook.handle_webhook(event_name=github_event, payload=request.json)
return Response("OK", status=200)
handle_webhookruns handlers sequentially and blocks until complete.- Exceptions are logged to
logging.getLogger('octohook')but don't stop execution.
URL Helpers
GitHub webhook payloads include URL templates with placeholders:
{
"repository": {
"archive_url": "https://api.github.com/repos/owner/repo/{archive_format}{/ref}"
}
}
Octohook models provide methods to interpolate these templates:
# event.repository is a Repository model with helper methods
>>> event.repository.archive_url("tarball")
'https://api.github.com/repos/owner/repo/tarball'
>>> event.repository.archive_url("tarball", "main")
'https://api.github.com/repos/owner/repo/tarball/main'
Limitations
GitHub sends different payload structures depending on the event type and action. Not all fields are present in all payloads, so octohook uses Optional types extensively. Fields are only marked as required if they appear in all known payloads for that model.
For example, the changes key only appears with edited actions. For other actions, event.changes is None.
Current test coverage is documented in tests/TestCases.md. PRs adding missing event payloads are welcome.
Related Skills
node-connect
353.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
353.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
353.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
