API og webhooks
Webhooks skubber hændelser ud til dine systemer, når de sker. Denne side indeholder alt, hvad der skal til for at verificere en — præcist nok til at implementere efter.
API-nøgler
Opret en nøgle under Indstillinger og derefter API-nøgler. Den begynder med la_live_ og vises én gang — vi gemmer kun et hash af den, så en mistet nøgle bliver erstattet frem for genfundet.
Nøgler hører til et workspace og ikke til en person, og de overlever, at personen holder op. Tilbagekald en, og den holder øjeblikkeligt op med at virke, overalt, også i den agent der brugte den.
En nøgle er også måden at tilslutte en MCP-klient, der ikke kan åbne en browser. Send den som bearer-token.
Hvad REST-API'et dækker i dag
Webhook-abonnementer og MCP-endepunktet er tilgængelige nu. Det fulde læse- og skrive-REST-API er en Operator-funktion planlagt til en senere fase, og denne side beskriver det, når det findes — ikke før.
Har du brug for at læse eller ændre noget programmatisk i dag, er MCP-værktøjerne den understøttede vej, og de virker fra en almindelig HTTP-klient lige så vel som fra en agent.
Webhooks
En webhook er et HTTPS-endepunkt hos dig plus en hemmelighed, vi genererer. Vi sender en JSON-payload til det, hver gang en hændelse, du abonnerer på, sker — signeret, så du kan bevise, at den kom fra os.
Webhooks findes fra Pro og opefter. Hver levering registreres med sine forsøg, svarkoden og den første del af svarets indhold, så en modtager, der stille afviser leveringer, er synlig frem for mystisk.
Hændelser
Navnene er en stabil del af grænsefladen. Vi kan tilføje til listen; vi omdøber ikke noget på den.
| Hændelse | Sendes når |
|---|---|
comment.captured |
En kommentar på et opslag, en af dine automatiseringer holder øje med, blev læst. |
lead.created |
Et nyt lead blev oprettet, uanset kilde. |
lead.email_captured |
Et lead afgav en e-mailadresse på en leadside. |
dm.sent |
En direkte besked blev leveret af din browser. |
dm.replied |
Nogen svarede på en besked, du sendte. |
connect.accepted |
En kontaktanmodning, du sendte, blev accepteret. |
approval.requested |
Noget venter i godkendelseskøen. |
approval.decided |
Nogen godkendte eller afviste en handling i køen. |
page.viewed |
En leadside blev vist. |
En webhook kan abonnere på en liste af disse eller på * for det hele — inklusive hændelser, der kommer til senere.
Signaturen
Hver levering signeres med HMAC over tidsstemplet og den rå payload. Tidsstemplet er inde i den signerede payload og ikke bare en header ved siden af — det er den del, de fleste genimplementeringer får galt, og det betyder noget: signerer man kun indholdet, får man en signatur, der gælder for evigt, og et usigneret tidsstempel kan skrives om og sætte dit eget friskhedstjek ud af kraft.
| Header | Betydning |
|---|---|
X-LeadAdapter-Signature |
HMAC af den signerede payload, som små bogstaver i hex, med algoritmen foran. |
X-LeadAdapter-Timestamp |
Unix-sekunder, UTC, på det tidspunkt dette forsøg blev signeret. Gensendelser signeres på ny med et nyt tidsstempel. |
X-LeadAdapter-Delivery |
Leverings-id'et. Det er det samme ved hver gensendelse af samme hændelse — brug det som din idempotensnøgle. |
X-LeadAdapter-Event |
Hændelsens navn, som også står i selve payloaden. |
Sådan beregnes signaturen
signed_payload = <X-LeadAdapter-Timestamp> + "." + <raw request body>
signature = "sha256=" + lowercase_hex(
HMAC_SHA256(signed_payload, webhook_secret)
)
Et gennemregnet eksempel
De tre input giver præcis denne signatur. Sæt dem ind i din egen implementering, og sammenlign — værdien nedenfor er beregnet af den samme kode, der signerer dine leveringer.
secret = whsec_example_do_not_use
timestamp = 1789012345
body = {"id":"01JBXQ8P2R4S6T8V0W2X4Y6Z8A","event":"lead.created"}
X-LeadAdapter-Signature: sha256=b959110ea48a9bb42c68cf1b0be05720089c84a7d05d92a24644144d8e55870f
Verificering, i PHP
$body = file_get_contents('php://input'); // raw, unparsed
$timestamp = $_SERVER['HTTP_X_LEADADAPTER_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_LEADADAPTER_SIGNATURE'] ?? '';
if (abs(time() - (int) $timestamp) > 300) {
http_response_code(400); exit; // outside the replay window
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
if (! hash_equals($expected, $signature)) {
http_response_code(400); exit; // constant-time, never ===
}
Gensendelser
Vi prøver igen ved 5xx, 429, 408 og ved transportfejl. Vi prøver ikke igen ved andre 4xx: dit endepunkt forstod kaldet og afviste det, og de samme bytes får det samme svar.
Hver gensendelse bærer det samme leverings-id og den samme payload, med et nyt tidsstempel og dermed en ny signatur. Det er med vilje, så en gensendelse, der kommer to timer senere, stadig består dit friskhedstjek.
| Forsøg | Pause inden |
|---|---|
| 2 | 10 sekunder |
| 3 | 1 minutter |
| 4 | 5 minutter |
| 5 | 30 minutter |
| 6 | 120 minutter |
Hvad din modtager skal gøre
Seks ting, i denne rækkefølge. Den første er den, der vælter implementeringer: læs de rå bytes, før noget JSON-mellemlag rører dem, for at gendanne indholdet ændrer det, og så passer signaturen ikke.
- Læs den rå payload, uden at parse den.
- Afvis leveringen, hvis tidsstemplet ligger længere fra dit eget ur end tolerancen.
- Genberegn signaturen, og sammenlign i konstant tid. Aldrig med en almindelig strengsammenligning.
- Først derefter: parse JSON.
- Fjern dubletter på leverings-id'et — gensendelser gentager det.
- Svar hurtigt med 2xx, og lav det egentlige arbejde bagefter.
Hver grænse, hvert loft, hver timeout og hvert værktøjsnavn på denne side læses fra det kørende produkt, når siden hentes. Ændrer softwaren sig, ændrer siden sig med.