---
title: Errors, alerts and form posts that become tasks by themselves
description: 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.
url: https://hamloy.com/blog/errors-alerts-and-forms-become-tasks
---

[Blog](https://hamloy.com/blog)

# 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=maren@example.com" \
  -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: maren@example.com",
 "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](https://hamloy.com/docs/api) 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](https://hamloy.com/docs/webhooks) explains how each part is found and what to do when one is read wrong.
