A webhook is an address you register. Tarkle sends a signed POST to it when something happens to a brand in the workspace. Webhooks are open to every plan. All calls here need the webhooks:manage scope, and the token’s creator must be an owner or admin of the workspace named in X-Workspace-Key. The Settings > API page in the app manages the same endpoints.
Register an endpoint
curl -X POST -H "Authorization: Bearer $TARKLE_TOKEN" -H "X-Workspace-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/tarkle", "events": ["brand.updated", "brand.downloaded"]}' \
https://tarkle.com/api/v1/webhooksThe answer (201) holds endpoint and secret. The secret (whsec_...) is shown once, in this answer only. Store it.
| Call | What it does |
|---|---|
GET /webhooks |
List the endpoints. |
POST /webhooks |
Add one. Body: url, events (optional) and description (optional). Up to 20 per workspace. |
GET, PATCH, DELETE /webhooks/{id} |
Read, change or remove one. PATCH takes url, events, description and enabled. Turning an endpoint on clears its failure count. |
POST /webhooks/{id}/rotate-secret |
Make a new secret. The old one also works for 24 hours. |
POST /webhooks/{id}/test |
Send one webhook.test event, once. It never changes the endpoint’s state. |
GET /webhooks/{id}/deliveries |
Every attempt, newest first. Kept for 7 days. |
POST /webhooks/{id}/deliveries/{delivery}/redeliver |
Send a delivery again as a fresh run. The endpoint must be on. |
The address. https, a public DNS name (no IP number, localhost or internal name), no username or password, port 443 or 1024 and above, up to 2,000 characters. Tarkle checks it when you add it and before every send. The connection goes to the checked address, redirects are not followed, and Tarkle waits 10 seconds for an answer.
Choose events
Send an events list of event types, or ["*"] for every event except page views. brand.viewed is only sent to an endpoint that names it, alone or next to "*".
| Event | When | data besides brand |
|---|---|---|
brand.viewed |
A visitor opens the brand page (a person, not a crawler, once per half hour). | occurred_at. No person, address or country. |
brand.downloaded |
A file or the whole kit is downloaded, from the page or with a kit link. | file (name, format), source (page or kit_link), occurred_at. Never the visitor. |
brand.updated |
The brand is edited in the app, its library is saved, a file is uploaded, or PATCH /brands/{brand} runs. |
changed (the names of what changed, never the values), source (app or api), occurred_at. An edit that changes nothing sends nothing. |
brand.request.created |
A visitor asks for access or to download. The same person asking again within a day is one request. | request (id, kind, name, company, email), created_at. The message is not sent. |
brand.request.approved, brand.request.rejected |
A request is decided in the app, in Slack or through the API. | The same request, and decided_at. |
data.brand always holds id, slug and name. kind is view or download.
What your server receives
A POST with Content-Type: application/json and this body:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody, header, secret) {
const pairs = header.split(",").map((part) => part.split("="));
const t = pairs.find(([key]) => key === "t")?.[1];
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
// During a rotation there are two v1 values; one match is enough.
return pairs
.filter(([key]) => key === "v1")
.some(([, hex]) => {
const given = Buffer.from(hex ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
});
}For 24 hours after a rotation the header carries two v1 values, one for the new secret and one for the old. Accept either.
Retries and health
Answer with any 2xx. Anything else (a redirect too), a timeout or a connection error is a failed try. Tarkle tries up to 8 times: after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 8 hours and 8 hours, about 24 hours in all.
410 Gonestops at once and switches the endpoint off.- An endpoint is also switched off after 5 days without one success, or when 20 deliveries in a row used all their tries. The workspace owner gets an email and
disabled_reasonsays why. - Turn it back on with
PATCH { "enabled": true }.