Errors, alerts and form posts that become tasks by themselves
Point Sentry, an uptime monitor or a contact form at a Hamloy webhook, or call the REST API from CI, and the work lands where your coding agents already look.
17 October 2026 · Rahmakyan
Most of the work your coding agents do starts somewhere else: an error in Sentry, a monitor that says the API is down, a customer's message through the contact form, a nightly test run that failed. Today you read it there, copy it into a chat, and explain it again. A Hamloy project can take that work in by itself. Give a service a webhook address and each thing it sends becomes a task, or call the REST API from a script for anything a service can't send. Either way it lands in the lists your agents already read, and the overview says where each task came from.
Everything below is from test runs on a local Hamloy server, on a project for a small plant shop called Fernwood, with the replies word for word.
A webhook for services, the API for your own scripts
The two do the same thing in the end, which is a task in the lists. They differ in who writes the request:
| Webhook | REST API | |
|---|---|---|
| Who sends | a service you don't control: Sentry, GitHub, an uptime monitor, a form | a script of yours: a CI job, a cron job, another app |
| What it sends | its own JSON, in its own shape | the fields Hamloy asks for |
| What it can do | add a task or a request | read, add, change, tick, answer and delete |
| Signed in the history as | webhook | api |
A webhook is made on the project's Settings page, under Webhooks: a name, the list it goes into, and an address to copy. The API takes a token from the Connect page.
An error from Sentry becomes a task
Fernwood has three webhooks: Sentry, into the backlog under the heading "Errors"; the contact form, under "From customers"; and an uptime monitor, under "Outages". Here is the part of Sentry's issue alert that matters, as Sentry sends it:
{"action": "created",
"data": {"issue": {
"shortId": "FERNWOOD-WEB-1F",
"title": "TypeError: Cannot read properties of undefined (reading 'price')",
"culprit": "app/checkout/summary.ts in lineTotal",
"project": {"name": "fernwood-web", …},
"metadata": {"type": "TypeError", "filename": "app/checkout/summary.ts", …},
"count": "37", "userCount": 21,
"web_url": "https://fernwood.sentry.io/issues/4410293817/",
"url": "https://sentry.io/api/0/organizations/fernwood/issues/4410293817/"}},
"actor": {"type": "application", "name": "Sentry"}}
Nothing was set up beyond the name and the list. Sentry got back:
{"key": "B-pzxb", "title": "TypeError: Cannot read properties of undefined (reading 'price')",
"url": "https://hamloy.com/p/…/items/B-pzxb", "outcome": "made B-pzxb"}
Hamloy weighs each piece of text by the name it is under and how deep it sits. title is the strongest name for a title, so the issue's title won over the project's name and the actor's. For the notes it took the culprit, the file and function the error came from. For the link it took web_url, the page a person opens, over url, which is Sentry's API. The project's name sits under project, which describes where the work came from rather than the work, so it was passed over.
The same error twice is one task
Sentry sends an alert again when its rule fires again. The second time, nothing new was made:
{"key": "B-pzxb", …, "outcome": "already open as B-pzxb"}
As long as a task with that title is open on that list, the same thing sent again is the same work. Once an agent ticks B-pzxb, the next one sent makes a new task, because the error came back.
A contact form keeps who wrote it
A form posts its fields as they are, without JSON. Fernwood's contact form sent four:
curl -X POST "https://hamloy.com/hooks?token=whk_…" \
-d "name=Maren Holt" -d "[email protected]" \
-d "subject=Gift card code says it's already used" \
-d "message=I bought a gift card for my sister on Saturday. She tried it today and the checkout says the code was already used. Order FW-20931."
The subject became the title and the message the notes. A form is flat, with no fields inside other fields, so the rest of it comes along under the message, and whoever works on it knows who to write back to. The task, as the REST API reads it back:
{"key": "B-crkw", "title": "Gift card code says it's already used",
"section": "From customers", "summary": "Sent in by the webhook Contact form",
"notes": "I bought a gift card for my sister on Saturday. She tried it today and the checkout says the code was already used. Order FW-20931.\n\nname: Maren Holt\nemail: [email protected]",
"added_by": "webhook", …}
When it reads the wrong part, point at the right one
An uptime monitor's alert names things its own way:
{"monitorFriendlyName": "Fernwood shop API", "monitorURL": "https://fernwood.shop/api/health",
"alertTypeFriendlyName": "Down", "alertDetails": "Connection Timeout", "alertDuration": "0", …}
Read by itself, the title came out as "Fernwood shop API", the monitor's name, because monitorFriendlyName ends in name. That says which monitor it is, not what happened. Under Where it reads each part, the webhook's settings show the last thing it was sent and which part each piece was read from, and take a path or words with paths in braces:
Title from {{monitorFriendlyName}} is {{alertTypeFriendlyName}}
Notes from alertDetails
Link from monitorURL
The next alert read as it should:
{"key": "B-ptpu", "title": "Fernwood shop API is Down", …, "outcome": "made B-ptpu"}
A path with dots reaches into nested JSON (issue.title) and a number picks one of a run (alerts.0.labels.alertname), so the same three boxes fit any service.
A nightly test run that keeps its own task
A service sends what it sends. A script can do more: look before it adds, add a note, and tick the task when the problem is gone. Fernwood's nightly end-to-end run ends with this step, given the result, the run's address and the failing spec:
#!/usr/bin/env bash
# After the nightly end-to-end run: a failure becomes a task, a pass ticks it.
set -euo pipefail
API="https://hamloy.com/api/v1/projects/$HAMLOY_PROJECT/items"
AUTH="Authorization: Bearer $HAMLOY_TOKEN"
TITLE="Nightly e2e run fails"
open=$(curl -sf -G "$API" -H "$AUTH" --data-urlencode "find=$TITLE" | jq -r --arg t "$TITLE" '.items[] | select(.title == $t and .done == false) | .key' | head -1)
if [ "$E2E_RESULT" = "failure" ]; then
if [ -n "$open" ]; then
curl -sf -X PATCH "$API/$open" -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg n "Failed again: $RUN_URL" '{add_note: $n}')" | jq -r '"\(.item.key) noted"'
else
curl -sf "$API" -H "$AUTH" -H "Content-Type: application/json" -d "$(jq -n --arg t "$TITLE" --arg u "$RUN_URL" --arg f "$FAILED" \
'{title: $t, section: "CI", summary: "Failing: \($f)", notes: "Run: \($u)", fields: {area: "ci", prio: "high"}}')" | jq -r '"\(.item.key) added"'
fi
elif [ -n "$open" ]; then
curl -sf -X POST "$API/$open/tick" -H "$AUTH" -H "Content-Type: application/json" \
-d "$(jq -n --arg n "Passed: $RUN_URL" '{note: $n}')" | jq -r '"\(.item.key) ticked"'
fi
Three nights, two failures and a pass:
night 1: B-yh2a added
night 2: B-yh2a noted
night 3: B-yh2a ticked
One task held the whole story, each run's address a note on it. Its history, through the list tool:
list(find: "B-yh2a", history: true)
Fernwood · changes of "B-yh2a", the latest first (UTC)
2026-10-17
18:33 B-yh2a "Nightly e2e run fails": api ticked it
18:33 B-yh2a "Nightly e2e run fails": api added a note: Passed: https://github.com/fernwood/shop/actions/runs/11935
18:33 B-yh2a "Nightly e2e run fails": api added a note: Failed again: https://github.com/fernwood/shop/actions/runs/11901
18:33 B-yh2a "Nightly e2e run fails": api added it
That is all of them.
Keep the token in the CI's secrets as HAMLOY_TOKEN, never in the repo. The REST API docs have every endpoint, and version for a change that must not overwrite someone else's.
What your agent sees
An agent doesn't need to know any of this happened. It reads the lists as always, and the work is there under its headings, each task saying where it came from:
list(kind: "task")
Fernwood · 3 open, 1 done
backlog
## From customers
[ ] B-crkw@1 Gift card code says it's already used (3 notes)
Sent in by the webhook Contact form
## Errors
[ ] B-pzxb@1 TypeError: Cannot read properties of undefined (reading 'price') (2 notes)
Sent in by the webhook Sentry
## Outages
[ ] B-ptpu@1 Fernwood shop API is Down (2 notes)
Sent in by the webhook Uptime
What arrives while an agent is at work reaches it with its next reply. Claude claimed the checkout error, and a second Sentry alert had come in meanwhile:
claim(key: "B-pzxb@1")
B-pzxb@1 "TypeError: Cannot read properties of undefined (reading 'price')" claimed by this session
…
Summary: Sent in by the webhook Sentry
Its notes:
app/checkout/summary.ts in lineTotal
https://fernwood.sentry.io/issues/4410293817/
Hamloy news (what changed since you last looked):
- A webhook added B-t147 "RangeError: Invalid time value"
Every change is signed by what made it: "webhook added it" for what a service sent, "api ticked it" for what a script did, beside what you and each agent did. On a shared project a script's changes are signed with its person's name, such as "Sara's api".
Start with the service that interrupts you most
Pick the one whose messages you copy into a chat most often, make a webhook for it on the project's Settings, and paste the address where the service asks for a webhook URL. Webhooks explains how each part is found and what to do when one is read wrong.