Create an approval form
Describe the decision as JSON. Give it a title, an optional description and a list of fields.
curl -X POST https://aisenseapi.com/services/v1/webhook_action \
-H "Content-Type: application/json" \
-d '{
"title": "Approve invoice INV-2043?",
"description": "Vendor total exceeds the auto-approval limit.",
"fields": [
{
"type": "radio",
"name": "decision",
"label": "Decision",
"required": true,
"options": [
{"value":"approve","label":"Approve"},
{"value":"reject","label":"Reject"}
]
},
{"type":"textarea","name":"comment","label":"Notes","max_length":500}
]
}'
{
"ok": true,
"action_id": "c2bcee60-016d-419e-a252-4e60600a2e2e",
"form_url": "https://aisenseapi.com/services/v1/webhook_action/c2bcee60-016d-419e-a252-4e60600a2e2e/form",
"result_url": "https://aisenseapi.com/services/v1/webhook_action/c2bcee60-016d-419e-a252-4e60600a2e2e",
"expire_timestamp": 1787266368,
"expire_datetime": "2026-08-20T22:52:48Z"
}
One call creates three things. The form_url is a real HTML page that opens in any browser. The result_url returns JSON for your workflow. The action_id appears inside both, so a single string is all you need to keep.
Nothing else is required. No key travels with the request, you host no receiver, and you open no inbound port.
Poll the result URL
Read the result with a plain GET. No header and no token are involved.
curl https://aisenseapi.com/services/v1/webhook_action/c2bcee60-016d-419e-a252-4e60600a2e2e
{
"ok": true,
"action_id": "c2bcee60-016d-419e-a252-4e60600a2e2e",
"status": "pending",
"created_at_timestamp": 1787179968,
"created_at_datetime": "2026-08-19T22:52:48Z",
"expire_timestamp": 1787266368,
"expire_datetime": "2026-08-20T22:52:48Z",
"answered_at_timestamp": null,
"answered_at_datetime": null,
"response": null
}
Until someone answers, status reads pending and response is null. Both answered fields stay null as well. Your loop therefore tests one condition rather than three.
Now the reviewer opens the form and submits it. The same URL changes shape.
{
"ok": true,
"action_id": "c2bcee60-016d-419e-a252-4e60600a2e2e",
"status": "answered",
"created_at_timestamp": 1787179968,
"created_at_datetime": "2026-08-19T22:52:48Z",
"expire_timestamp": 1787266368,
"expire_datetime": "2026-08-20T22:52:48Z",
"answered_at_timestamp": 1787179994,
"answered_at_datetime": "2026-08-19T22:53:14Z",
"response": {
"decision": "approve",
"comment": "Budget confirmed by finance"
}
}
Every answer sits under response, keyed by the name you gave that field when you created the action. Call a field decision and your code reads response.decision. The shape of the answer is the shape you asked for, so no parsing of free-form replies is needed.
Field types the free webhook action API endpoint accepts
Five types cover the decisions most pipelines hand to a person.
| Type | Best for | Important properties |
|---|---|---|
| radio | One visible choice from a short list | options, required |
| select | One choice from a longer list | options, required |
| text | Short free-form answers | max_length, required |
| textarea | Comments, reasons and instructions | max_length, required |
| checkbox | Confirmation or several independent choices | options, required |
Options take either of two shapes. Plain strings work when the stored value and the visible label are the same. Objects with separate value and label properties work when they differ, which keeps machine-readable codes out of the reviewer's view.
{
"title": "Route the support ticket",
"fields": [
{"type":"select","name":"queue","label":"Queue","required":true,
"options":["billing","technical","sales"]},
{"type":"text","name":"owner","label":"Assign to","max_length":40},
{"type":"checkbox","name":"flags","label":"Flags",
"options":["urgent","needs_manager"]}
]
}
That body builds a routing form with a required queue, a free-text owner capped at forty characters, and two flags the reviewer can tick independently.
Response fields
A poll returns the same set of fields whether the action has been answered or not.
| Field | Type | Description |
|---|---|---|
| action_id | string | The UUID that identifies this action in both URLs. |
| status | string | pending before submission, answered after it. |
| created_at_timestamp | integer | Unix time when the action was created. |
| created_at_datetime | string | The same moment as an ISO 8601 timestamp in UTC. |
| expire_timestamp | integer | Unix time exactly 24 hours after creation. |
| expire_datetime | string | The expiry moment in ISO 8601 UTC form. |
| answered_at_timestamp | integer | Unix time of the submission, or null while pending. |
| answered_at_datetime | string | The submission moment in ISO 8601 UTC, or null. |
| response | object | The submitted values keyed by field name, or null. |
Errors and repeat submissions
An unknown or expired action id answers with HTTP 404 and a short error object.
{"error":"Action id unknown"}
A second submission on a form that already carries an answer is refused with HTTP 409.
{"error":"Action already answered"}
That refusal earns its place. Each action is a one-shot decision, so a reviewer who reloads the page cannot quietly overwrite a verdict your pipeline has already acted on. The free webhook action API endpoint keeps the first answer and rejects the rest.
How a pause for a human works
Three steps sit between an automated run and a human verdict.
- Describe the decision
POST a title, an optional description and one or more fields. The reply carries the two URLs you need.
- Deliver the form URL
Send it through email, chat, a ticket comment or whatever channel the reviewer already watches. They open a page and press submit. There is no login and no app to install.
- Poll until the status changes
Resume the workflow when
statusflips toanswered, and branch on the values insideresponse.
The hosted page is deliberately ordinary. It is a plain HTML form that posts back to its own address, and it carries no script tags at all. That keeps it usable in a locked-down corporate browser or on a phone, and it means a reviewer who has never seen your pipeline still knows what to do with it.
Where a human decision belongs
Teams reach for the free webhook action API endpoint whenever a rule cannot settle something safely on its own. Four patterns come up again and again.
Deployment gates
Require an explicit go or no-go before a production change continues.
Agent escalation
Let an AI agent ask a person when it reaches a risky or ambiguous step.
Content review
Collect approval, rejection and revision notes in one predictable shape.
Exception handling
Pause an automation when a transaction or document needs human judgment.
The pattern also pairs well with an inbound listener. Capture a third-party callback with the webhook capture API endpoint, inspect what arrived, then raise an action when the payload looks wrong.
Expiry, security and limits
Actions expire 24 hours after creation. Design the caller to handle an action that is never answered, rather than polling forever.
Both URLs are unguessable capability URLs. Anyone holding one can open the form or read the answer, so send them to the intended reviewer and to nobody else. Confidential material, long retention and account-based access control belong in a system built for those things.
The base URL is https://aisenseapi.com/services/v1. No key and no account are required. Every service on the free public REST APIs hub shares one limit of 5000 requests per IP per 24 hours, so the create call and each poll count against the same budget. Poll on a sensible interval instead of a tight loop.
Neighbouring services fill the gaps. Park a large payload in the storage API endpoint and put only its id in the description. Shorten a long form link with the URL shortener API endpoint before pasting it into a chat message. Check a payload against a rule with the validation API endpoint first, and raise an action only for the cases that fail. Used that way, the free webhook action API endpoint becomes the one step in a pipeline where a person is genuinely required.