POST /v1/actions/hooks/{hook_id}
The URL B3OS registers with a provider for a webhook trigger subscription (`tsub_…`). The delivery is verified with the trigger's `verify` scheme from its pack manifest, deduplicated by its dedupe key, filtered, and turned into events for the subscription's consumers. Timestamped schemes (`stripe`, `slack`, `standard_webhooks`, and `hmac_sha256`, which always is) refuse a delivery whose timestamp is more than `verify.toleranceS` seconds (at most 300) from the node clock: `401 webhook_signature_invalid` with `reason: timestamp`, so a captured delivery cannot be replayed later; inside the window a repeat is a duplicate (the dedupe key is remembered for 73 hours, up to 10,000 keys per subscription). Providers that send a verification handshake (Slack's `url_verification`) get the answer the manifest's `handshake` defines. A paused subscription, a filtered delivery and an authentic delivery whose event does not match the trigger's `eventSchema` are acknowledged with `202` and emit nothing. No API key is used: the unguessable hook ID plus the provider signature authenticate it. The verified signature is remembered with the dedupe key, so a captured delivery replayed with a new value in an unsigned header (GitHub's `X-GitHub-Delivery`) is still a duplicate; `github` signs no timestamp, so such a replay is refused only while the signature is remembered (73 hours, up to 10,000 per subscription). While the node is unreachable the edge queues deliveries (`202` with `x-b3os-queued: true`) and replays them in order, attesting when it received each one: `x-b3os-hook-received-at` (Unix seconds), `x-b3os-edge-kid` and `x-b3os-edge-sig`, an Ed25519 signature by the edge key over `b3os-hook-v1\n<node>\n<path>\n<received_at>\n<hex sha256(body)>`, plus `x-b3os-hook-replay` (the attempt, 0 first). The node verifies the attestation when it is present (`401 webhook_signature_invalid`, `reason: signature` or `timestamp`, when it does not verify or the receive time is more than 300 seconds ahead or 72 hours plus 300 seconds behind the node clock) and, on a replay (`x-b3os-hook-replay` ≥ 1), applies the provider's timestamp tolerance to the edge's receive time instead of its own clock, so a delivery queued during an outage is not refused as stale. The provider signature is required either way. A replay signed by an edge key id this node does not trust yet, whose provider timestamp is outside the tolerance of the node clock, answers `503 hook_attestation_unverifiable` with `Retry-After: 60` (the same rule as the routines hook route). The node answers 5xx only when a later attempt can succeed (also: the vault, the routine engine or the pinned pack version is not available right now, `503 product_unavailable` or `503 pack_unavailable`); the edge keeps those deliveries and retries them. A subscription whose signing secret cannot be used answers `422 connection_required` or `422 connection_mismatch`.

Path Parameters

hook_id string required path
The hook ID (`hk_…`) of the trigger subscription.
Example: "hk_01m5t6qg7cfcq459vcqvpj74jj"

Request Body

application/json
unknown

Responses

200 A verification handshake was answered (the body is what the provider expects).
application/json
object
202 The delivery was accepted (events emitted, a duplicate, or filtered out).
400 The request failed validation (`invalid_request`, `invalid_url`).
401 Verification failed (`webhook_signature_invalid`): the provider signature or timestamp, or the edge attestation.
404 No such hook (`hook_not_found`).
413 The request body exceeds this operation's limit.
415 The Content-Type is not accepted by this operation.
422 The trigger's dedupe key, filter, handshake or event mapping failed on this verified delivery (`action_expression_error`), or the subscription's signing secret cannot be used (`connection_required`, `connection_mismatch`).
429 A rate, concurrency or quota limit applies (`rate_limited`, `concurrency_limited`, `quota_exceeded`).
500 Something failed inside B3OS (`internal`). Retry with backoff.
503 The node or the product is not available right now (`node_unavailable`, `product_unavailable`, `capacity_unavailable`).
curl -X POST 'https://beta-api.b3os.org/v1/actions/hooks/hk_01m5t6qg7cfcq459vcqvpj74jj' \  -H 'Authorization: Bearer YOUR_API_TOKEN' \  -H 'Content-Type: application/json' \  -d '{  "id": "evt_1Q2w3E4r5T6y7U8i",  "type": "invoice.paid"}'
const response = await fetch('https://beta-api.b3os.org/v1/actions/hooks/hk_01m5t6qg7cfcq459vcqvpj74jj', {  method: 'POST',  headers: {      "Authorization": "Bearer YOUR_API_TOKEN",      "Content-Type": "application/json"  },  body: JSON.stringify({    "id": "evt_1Q2w3E4r5T6y7U8i",    "type": "invoice.paid"  })});const data = await response.json();console.log(data);
import requestsheaders = {    'Authorization': 'Bearer YOUR_API_TOKEN'}response = requests.post('https://beta-api.b3os.org/v1/actions/hooks/hk_01m5t6qg7cfcq459vcqvpj74jj', headers=headers, json={  "id": "evt_1Q2w3E4r5T6y7U8i",  "type": "invoice.paid"})print(response.json())
200 Response
{  "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"}
Ask a question... ⌘I