- name
- webhooks
- description
- Accept webhook POSTs on localhost, answer quickly, and ignore duplicate retries. Use when another service needs to notify this machine.
# Webhooks
Listen on localhost for JSON POST requests, store each new event, and answer `204` before doing slow work. A second delivery of the same `id` is ignored.
## When to use
Some other program needs to notify this machine with an HTTP POST, and you can point it at `127.0.0.1`. The listener below is the standard-library handler for that. It writes events to `events.jsonl` in the current directory.
## Install
Python 3 is the only dependency. There is no package to install.
```sh
python3 -c "import http.server; print('ok')"
```
Save this as `webhook_receive.py`:
```python
#!/usr/bin/env python3
"""Receive webhook POSTs on localhost and append new events to events.jsonl."""
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
HOST = "127.0.0.1"
PORT = 8787
OUT = Path("events.jsonl")
MAX_BODY = 1_000_000
def known_ids():
if not OUT.exists():
return set()
ids = set()
for line in OUT.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
event_id = event.get("id")
if isinstance(event_id, str):
ids.add(event_id)
return ids
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path.split("?", 1)[0] != "/hook":
self.send_error(404)
return
try:
n = int(self.headers.get("Content-Length", "0"))
except ValueError:
self.send_error(400)
return
if n <= 0 or n > MAX_BODY:
self.send_error(413 if n > MAX_BODY else 400)
return
raw = self.rfile.read(n)
try:
event = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
self.send_error(400)
return
if not isinstance(event, dict):
self.send_error(400)
return
event_id = event.get("id")
if isinstance(event_id, str) and event_id in known_ids():
self.send_response(204)
self.end_headers()
return
with OUT.open("a", encoding="utf-8") as fh:
fh.write(json.dumps(event, separators=(",", ":")) + "\n")
self.send_response(204)
self.end_headers()
def log_message(self, fmt, *args):
return
if __name__ == "__main__":
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
```
## Commands
Start the listener, then send an event. A repeat of the same `id` still returns `204` and does not add a second line.
```sh
python3 webhook_receive.py
```
```sh
curl -sS -D - -o /dev/null -X POST http://127.0.0.1:8787/hook \
-H 'Content-Type: application/json' \
-d '{"id":"evt_1","type":"ping"}'
```
Read what was stored:
```sh
cat events.jsonl
```
## Gotchas
- The server binds to `127.0.0.1` only. A sender on another machine cannot reach it until you change `HOST` on purpose.
- Answer with `204` as soon as the event is stored. A non-2xx status is a signal for the sender to retry.
- The same `id` is stored once. The second POST still returns `204`, which tells the sender to stop retrying.
- Bodies that are empty, not JSON, or not an object get `400`. Bodies over 1,000,000 bytes get `413`.
- The handler stores the JSON. It does not execute it.
---
Also on Honeypot — a free, open index of skills for AI agents: https://honeypot-e6c.pages.dev
[](https://honeypot-e6c.pages.dev)
GitHub에서 보기