Webhooks
Verify the webhooks Cosmoner sends you, register endpoints, and inspect or replay deliveries from code.
client.webhooks does two jobs: it verifies the requests Cosmoner sends to
your endpoint, and it manages the endpoints themselves. Verifying needs no API
key. Managing endpoints needs webhooks:read, and webhooks:write to change
anything.
The Webhooks guide lists every event, the request format and the retry schedule. This page covers the SDK side.
Verify an incoming webhook
Every webhook carries an x-cosmoner-signature header. constructEvent
checks it against your endpoint's signing secret and returns the parsed event.
If the signature does not hold, it raises WebhookSignatureError instead, so
there is no event to act on by accident.
Pass the raw request body, exactly as it arrived. Parsing the JSON and serialising it again changes the bytes, and the signature will not match.
import express from "express";
import { constructEvent, WebhookSignatureError } from "@cosmoner/sdk";
const app = express();
app.post("/webhooks/cosmoner", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
event = constructEvent({
payload: req.body, // a Buffer, thanks to express.raw()
signature: req.header("x-cosmoner-signature") ?? "",
secret: process.env.COSMONER_WEBHOOK_SECRET!,
});
} catch (err) {
if (err instanceof WebhookSignatureError) return res.sendStatus(400);
throw err;
}
if (event.type === "app.deployed") {
// ...
}
res.sendStatus(200);
});The event has an id, a type, a createdAt time and a data payload. The
same delivery can arrive more than once, so use the
x-cosmoner-delivery-id header or the event id to skip ones you have
already handled.
A signature older than five minutes is rejected, so a captured request cannot
be replayed at you later. Pass toleranceSeconds (tolerance_seconds in
Python) to change that window, or 0 to turn the age check off.
When you only need a yes or no, verifyWebhookSignature (verify_webhook_signature
in Python, WebhookSignature::verify in PHP) takes the same arguments and
returns a boolean. It also exists on the client as client.webhooks.verify()
in JavaScript and PHP.
Register an endpoint
const { data: endpoint } = await client.webhooks.create({
name: "Deploy notifications",
url: "https://example.com/webhooks/cosmoner",
events: ["app.deployed", "app.failed"],
});
// Store this now — no later call returns it.
console.log(endpoint.secret);The URL must be HTTPS. The signing secret is returned only by create;
every later read gives just secretHint, its last four characters. If you lose
it, issue a new one with rotateSecret.
The full list of event types is exported as WEBHOOK_EVENT_TYPES in
JavaScript and Python, and WebhooksService::EVENT_TYPES in PHP.
Manage endpoints
| Method | Description |
|---|---|
list() | Every endpoint in the project. |
get(endpointId) | One endpoint, with counts of succeeded, failed and pending deliveries. |
create(...) | Registers an endpoint and returns it with its signing secret. |
update(endpointId, ...) | Changes name, url, events, description or enabled. Re-enabling an endpoint also clears its failure streak. |
delete(endpointId) | Deletes the endpoint and its delivery history. |
resume(endpointId) | Re-enables an endpoint that paused itself after repeated failures, and re-queues what was held back. |
rotateSecret(endpointId) | Issues a new signing secret and returns it. The old one stops working immediately. |
test(endpointId, ...) | Sends a test event and reports how the endpoint answered. Optionally takes an eventType. |
listDeliveries(endpointId, ...) | Delivery attempts, newest first. Filter by status or eventType, page with limit and cursor. |
replayDelivery(endpointId, deliveryId) | Sends a delivery's payload again. The endpoint must be enabled. |
Method names are snake_case in Python: rotate_secret, list_deliveries,
replay_delivery.
Rotating takes effect at once, so deliveries signed with the new secret fail verification until your receiver has it. If you cannot afford rejected deliveries, deploy the receiver so it accepts both secrets first, then rotate.