/v1/account/webhook-endpoints
account/webhook-endpoints
Webhookit: minne Monitoring lähettää uudet Signals-tapahtumat ja miten tarkistat ne.
Webhook-päätepiste on palvelimesi HTTPS-osoite, johon lähetämme monitoriesi uudet signaalit. Jokainen viesti on allekirjoitettu, joten voit tarkistaa, että se tulee meiltä.
Kustannukset ja rajat
| Mitä | Krediitit |
|---|---|
| Webhook-päätepisteiden ja toimitusten hallinta | maksuton |
Raja: 30 pyyntöä minuutissa. Luonti, muutos ja poisto: 10 minuutissa. Tarkistus, testi ja uusi secret: 6 minuutissa.
Jokaisella tilillä voi olla enintään 10 webhook-päätepistettä; arkistoituja ei lasketa. Jokainen webhook-päätepiste voi kuljettaa enintään 10 mukautettua headeria.
Admin-token tai API-avain
Tarvitset admin-tokenin allekirjoitussecretin luomiseen sekä webhook-päätepisteiden käyttöönottoon, käytöstä poistoon ja arkistointiin. Luot sen kohdassa Tili › API-avaimet osiossa ”Kehittäjille: avainten hallinta APIn kautta”. Se on voimassa vuoden, ja uusi korvaa vanhan.
Lähetä admin-token muodossa Authorization: Bearer YOUR_ADMIN_TOKEN. Kaikki muut kutsut toimivat myös API-avaimella.
Kuten päätepisteessä account/monitors, kaikki kirjoituskutsut tarvitsevat headerin Idempotency-Key.
Päätepisteet
| Metodi | Polku | Tarkoitus | Pääsy |
|---|---|---|---|
| GET |
/v1/account/webhook-endpoints |
Kaikki webhook-päätepisteet ilman arkistoituja. | API-avain tai admin-token |
| POST |
/v1/account/webhook-endpoints |
Luo webhook-päätepiste. | Admin-token |
| POST |
/v1/account/webhook-endpoints/{id}/verify |
Vahvista. | API-avain tai admin-token |
| POST |
/v1/account/webhook-endpoints/{id}/test |
Lähetä testiviesti. | API-avain tai admin-token |
| POST |
/v1/account/webhook-endpoints/{id}/rotate-secret |
Uusi allekirjoitussecret. | Admin-token |
| POST |
/v1/account/webhook-endpoints/{id}/enable |
Ota käyttöön. | Admin-token |
| POST |
/v1/account/webhook-endpoints/{id}/disable |
Poista käytöstä. | Admin-token |
| DELETE |
/v1/account/webhook-endpoints/{id} |
Arkistoi. | Admin-token |
| GET |
/v1/account/webhook-deliveries |
Viimeiset 50 toimitusta. | API-avain tai admin-token |
| POST |
/v1/account/webhook-deliveries/{id}/retry |
Lähetä epäonnistunut toimitus uudelleen. | API-avain tai admin-token |
| GET |
/v1/account/webhook-events |
Viimeiset 50 tapahtumaa. | API-avain tai admin-token |
Luo webhook-päätepiste
curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints" \ -H "Authorization: Bearer SINUN_ADMIN_TOKENISI" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{ "name": "CRM-Sync", "url": "https://hooks.example.com/handelsregister", "headers": { "X-Receiver-Token": "SINUN_VASTAANOTTAJA_TOKENISI" } }'
import uuid import requests response = requests.post( "https://handelsregister.ai/api/v1/account/webhook-endpoints", headers={ "Authorization": "Bearer SINUN_ADMIN_TOKENISI", "Idempotency-Key": str(uuid.uuid4()), }, json={ "name": "CRM-Sync", "url": "https://hooks.example.com/handelsregister", "headers": { "X-Receiver-Token": "SINUN_VASTAANOTTAJA_TOKENISI" } }, ) data = response.json()
const response = await fetch("https://handelsregister.ai/api/v1/account/webhook-endpoints", { method: "POST", headers: { "Authorization": "Bearer SINUN_ADMIN_TOKENISI", "Idempotency-Key": crypto.randomUUID(), "Content-Type": "application/json", }, body: JSON.stringify({ "name": "CRM-Sync", "url": "https://hooks.example.com/handelsregister", "headers": { "X-Receiver-Token": "SINUN_VASTAANOTTAJA_TOKENISI" } }), }); const data = await response.json();
| Nimi | Tyyppi | Kuvaus | Esimerkki |
|---|---|---|---|
| Idempotency-KeyHeader | merkkijonopakollinen | Yksilöllinen arvo toimintoa kohden, mieluiten UUID. Katso Idempotency-Key.Rajat: 1–128 merkkiä |
req-20260802-01 |
| nameBody | merkkijonopakollinen | Nimi omaa käyttöäsi varten.Rajat: 1–120 merkkiä |
CRM-Sync |
| urlBody | merkkijono (uri)pakollinen |
Palvelimesi osoite: julkisesti tavoitettava, https portissa 443, isäntänimellä IP-osoitteen sijaan.Rajat: 1–2 048 merkkiä |
https://hooks.example.com/handelsregister |
| headersBody | objektivalinnainen | Mukautetut headerit, jotka lähetetään jokaisen viestin mukana, kuten token palvelimellesi.Rajat: enintään 10 kohdetta |
{"X-Receiver-Token":"SINUN_VASTAANOTTAJA_TOKENISI"} |
Headerien nimet eivät saa alkaa webhook- eivätkä korvata vakioheadereita, kuten Content-Type tai Host. Arvot ovat vain kirjoitettavia; myöhemmin API näyttää vain nimet.
Vastaus sisältää allekirjoitussecretin whsec_…. Näytämme sen vain tämän kerran. Tallenna se heti.
1{2 "endpoint": {3 "id": "wep_01m6b3j1g0cdz98hw8zdefbf52",4 "name": "CRM-Sync",5 "url": "hooks.example.com/…",6 "status": "pending_verification",7 "verified_at": null,8 "header_names": [9 "X-Receiver-Token"10 ],11 "consecutive_failures": 0,12 "created_at": "2026-11-01T08:28:01+00:00"13 },14 "signing_secret": "whsec_…",15 "meta": {16 "message": "Endpoint created. Store the signing secret now — it is shown only once.",17 "request_credit_cost": 0,18 "credits_remaining": 14519 }20}
Uudella webhook-päätepisteellä on tila pending_verification, ja se vastaanottaa viestejä vasta vahvistuksen jälkeen. Nimeä, URL-osoitetta ja headereita ei voi muuttaa myöhemmin. Uutta osoitetta varten luo uusi webhook-päätepiste.
Vastauksissa url näyttää vain hostin; lyhennämme polun muotoon /….
Vahvista
Pyynnöllä POST …/{id}/verify lähetämme URL-osoitteeseesi allekirjoitetun viestin, jonka tyyppi on endpoint.verification. data.challenge sisältää satunnaisen arvon.
Palvelimesi vastaa 15 sekunnin sisällä 2xx-tilalla ja palauttaa arvon vastausheaderissa webhook-verification. Webhook-päätepiste muuttuu silloin tilaan active.
1{2 "id": "msg_01m6b3jsqq7ntwzkdj099ayraz",3 "type": "endpoint.verification",4 "timestamp": "2026-11-01T08:28:26.743Z",5 "schema_version": 1,6 "data": {7 "challenge": "189fb34b2af819bb13cb47d9806dbf30"8 }9}
curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/verify" \ -H "x-api-key: SINUN_API_AVAIMESI" \ -H "Idempotency-Key: $(uuidgen)"
import uuid import requests response = requests.post( "https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/verify", headers={ "x-api-key": "SINUN_API_AVAIMESI", "Idempotency-Key": str(uuid.uuid4()), }, ) data = response.json()
const response = await fetch("https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/verify", { method: "POST", headers: { "x-api-key": "SINUN_API_AVAIMESI", "Idempotency-Key": crypto.randomUUID(), }, }); const data = await response.json();
Jos se onnistuu, API vastaa "verified": true, muuten 422 ja "verified": false. Uuteen yritykseen tarvitset uuden Idempotency-Key-arvon.
Jokainen webhook-päätepiste sallii 6 yritystä tunnissa.
1{2 "verified": true,3 "endpoint": {4 "id": "wep_01m6b3j1g0cdz98hw8zdefbf52",5 "name": "CRM-Sync",6 "url": "hooks.example.com/…",7 "status": "active",8 "verified_at": "2026-11-01T08:28:26+00:00",9 "header_names": [10 "X-Receiver-Token"11 ],12 "consecutive_failures": 0,13 "created_at": "2026-11-01T08:28:01+00:00"14 },15 "meta": {16 "request_credit_cost": 0,17 "credits_remaining": 14518 }19}
Sen jälkeen POST …/{id}/test lähettää allekirjoitetun testiviestin, jonka tyyppi on endpoint.test, milloin haluat. Vastaus näyttää toimituksen tuloksen.
1{2 "delivery": {3 "id": "del_01m6b3jsyrnc3sz1mt31dq6107",4 "message_id": "msg_01m6b3jsynxzhvagm7n2nq7mpv",5 "event_id": "evt_01m6b3jsynxzhvagm7n2nq7mpt",6 "event_type": "endpoint.test",7 "endpoint_id": "wep_01m6b3j1g0cdz98hw8zdefbf52",8 "status": "succeeded",9 "attempts": 1,10 "last_status_code": 200,11 "last_error_category": null,12 "next_attempt_at": "2026-11-01T08:28:26+00:00",13 "succeeded_at": "2026-11-01T08:28:26+00:00",14 "created_at": "2026-11-01T08:28:26+00:00"15 },16 "meta": {17 "request_credit_cost": 0,18 "credits_remaining": 14519 }20}
Tarkista allekirjoitus
Jokainen viesti on allekirjoitettu avoimen standardin Standard Webhooks mukaisesti. Tarkista allekirjoitus ennen kuin käsittelet viestin.
| Header | Merkitys |
|---|---|
| webhook-id |
Viestin tunniste (msg_…). Se pysyy samana uudelleenyrityksissä. Tunnista sillä kaksoiskappaleet.
|
| webhook-timestamp | Tämän toimitusyrityksen ajankohta Unix-sekunteina. |
| webhook-signature |
Yksi tai useampi allekirjoitus muodossa v1,<Base64>, välilyönnein erotettuina.
|
Näin tarkistat viestin:
- Ota secretistä osa
whsec_:n jälkeen ja pura se Base64-koodauksesta. Se on avain. - Muodosta teksti
webhook-id.webhook-timestamp.Bodykahdesta headerista ja muuttamattomasta rungosta. - Laske siitä HMAC-SHA256 avaimella ja koodaa tulos Base64-muotoon.
- Vertaa tulosta vakioajassa jokaiseen headerin
v1-allekirjoitukseen. Jos jokin täsmää, viesti on aito.
Käytä runkoa täsmälleen sellaisena kuin se saapuu, ei uudelleen sarjallistettua JSONia. Hylkää viestit, joiden aikaleima poikkeaa yli viisi minuuttia.
import base64, hashlib, hmac, time def verify_webhook(secret: str, headers: dict, body: bytes) -> bool: msg_id = headers["webhook-id"] timestamp = headers["webhook-timestamp"] # Hylkää yli viisi minuuttia vanhat viestit if abs(time.time() - int(timestamp)) > 300: return False key = base64.b64decode(secret.removeprefix("whsec_")) signed = f"{msg_id}.{timestamp}.".encode() + body expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode() # Secretin vaihdon aikana headerissa on kaksi allekirjoitusta for part in headers["webhook-signature"].split(" "): version, _, signature = part.partition(",") if version == "v1" and hmac.compare_digest(signature, expected): return True return False
import crypto from "node:crypto"; export function verifyWebhook(secret, headers, rawBody) { const msgId = headers["webhook-id"]; const timestamp = headers["webhook-timestamp"]; // Hylkää yli viisi minuuttia vanhat viestit if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const expected = crypto .createHmac("sha256", key) .update(`${msgId}.${timestamp}.${rawBody}`) .digest(); // Secretin vaihdon aikana headerissa on kaksi allekirjoitusta return headers["webhook-signature"].split(" ").some((part) => { const [version, signature] = part.split(","); const given = Buffer.from(signature ?? "", "base64"); return version === "v1" && given.length === expected.length && crypto.timingSafeEqual(given, expected); }); }
Oman koodin sijaan voit käyttää virallisia Standard Webhooks -kirjastoja, kuten pakettia standardwebhooks Pythonille ja JavaScriptille.
Pyynnöllä POST …/{id}/rotate-secret saat uuden secretin kerran vastauksessa. 7 päivän ajan viesteissä on kaksi allekirjoitusta, yksi uudella ja yksi vanhalla secretillä. Näin vaihdat ilman katkoa.
Payload
Jokainen viesti on JSON-objekti samalla kuorella. Tapahtuman sisältö on kentässä data.
| Kenttä | Merkitys |
|---|---|
| id |
Viestin tunniste, sama kuin header webhook-id.
|
| event_id |
Tapahtuman tunniste (evt_…).
|
| type | Tapahtuman tyyppi, katso alla. |
| timestamp | Milloin havaitsimme tapahtuman, UTC-ajassa. |
| schema_version |
Rakenteen versio, tällä hetkellä 1.
|
| data | Tapahtuman data. |
| Tapahtumatyyppi | Merkitys |
|---|---|
| organization.signal.detected |
Monitori on löytänyt uuden signaalin. data.signal on rakenteeltaan sama kuin signaali päätepisteessä signals, lisäksi data.monitor ja kentässä data.links.signal linkki signaaliin API:ssa.
|
| organization.signal.sample |
Esimerkki yrityksen todellisesta historiasta, lähetetty kohdasta Tili › Monitorointi. Sama rakenne kuin yllä, lisäksi data.sample: true.
|
| endpoint.test |
Testiviesti polun /test kautta.
|
| endpoint.verification | Haaste vahvistuksessa. |
1{2 "id": "msg_01m65pqg9x0176cteqvtxw68sa",3 "event_id": "evt_01m65pqg9sx7mwmw0edc1mfbbe",4 "type": "organization.signal.sample",5 "timestamp": "2026-10-30T06:07:37.000Z",6 "schema_version": 1,7 "data": {8 "monitor": {9 "id": "mon_01m65pn3vm11sjjp1j076cbvec",10 "type": "organization.signals"11 },12 "sample": true,13 "signal": {14 "event": {15 "id": "36c7c8fddf99bb0299d6815bcaa01013",16 "topic": "TRANSFORMATIONS",17 "topic_name": {18 "de": "Umwandlungen & M&A",19 "en": "Transformations & M&A"20 },21 "occurred_on": "2022-12-22",22 "announced_on": "2023-08-10",23 "date_basis": "SOURCE_EVENT"24 },25 "organization": {26 "type": "ORGANIZATION",27 "entity_id": "94eb910fef13f94585c48c263fa446d4",28 "current_profile": {29 "name": "Hetzner Online GmbH",30 "registration": {31 "register_type": "HRB",32 "register_number": "6089",33 "court": "Ansbach",34 "register_locality": "Ansbach",35 "registration_authority_id": "D3201",36 "register_entity_name": "Ansbach HRB 6089"37 },38 "legal_form": {39 "name": {40 "de": "Gesellschaft mit beschränkter Haftung"41 },42 "short": {43 "de": "GmbH"44 }45 },46 "seat": {47 "city": "Gunzenhausen"48 },49 "status": {50 "code": "ACTIVE"51 }52 }53 },54 "parties": {55 "counterparty": {56 "entity": {57 "type": "ORGANIZATION",58 "entity_id": "dd4d2d6854b2dbd1ffce74e669f3852e",59 "current_profile": {60 "name": "Headtrip GmbH",61 "registration": {62 "register_type": "HRB",63 "register_number": "33432",64 "court": "Nürnberg",65 "register_locality": "Nuremberg",66 "registration_authority_id": "D3310",67 "register_entity_name": "Nürnberg HRB 33432"68 },69 "legal_form": {70 "name": {71 "de": "Gesellschaft mit beschränkter Haftung"72 },73 "short": {74 "de": "GmbH"75 }76 },77 "seat": {78 "city": "Nürnberg"79 },80 "status": {81 "code": "INACTIVE"82 }83 }84 },85 "relationship": "TRANSFERRING_COMPANY"86 }87 },88 "register_entry": {89 "entry_number": "3",90 "entry_date": "2023-08-10",91 "description_text": "Verschmelzung zur Aufnahme der Headtrip GmbH",92 "context": "PRIMARY",93 "flags": []94 },95 "source": {96 "kind": "COMMERCIAL_REGISTER"97 },98 "details": {99 "schema": "TRANSFORMATION",100 "kind": "MERGER",101 "specifics": {102 "merger_target": "ABSORPTION"103 },104 "direction": "INCOMING",105 "is_cross_border": false,106 "resolution_date": "2022-12-22",107 "agreement_date": "2022-12-22"108 }109 },110 "links": {111 "signal": "https://handelsregister.ai/api/v1/signals/36c7c8fddf99bb0299d6815bcaa01013"112 }113 }114}
Viesti on enintään 20 kt. Jos signaali kasvaa suuremmaksi, details ja register_entry jätetään pois ja data.truncated on true. Haet silloin koko signaalin linkin kautta.
Ohita tapahtumatyypit, joita et tunne, ja vastaa silti 2xx. Näin palvelimesi pysyy vakaana, kun uusia tyyppejä tulee.
Toimitukset ja uudelleenyritykset
Toimitus katsotaan onnistuneeksi, kun palvelimesi vastaa 15 sekunnin sisällä 2xx-tilalla. Emme seuraa uudelleenohjauksia.
Muuten yritämme uudelleen: enintään 10 yritystä kasvavin välein noin kolmen päivän aikana. Jos palvelimesi lähettää headerin Retry-After, noudatamme sitä.
Jos palvelimesi vastaa 410, lopetamme toimittamisen ja poistamme webhook-päätepisteen käytöstä.
Jos 5 peräkkäistä toimitusta epäonnistuu lopullisesti, webhook-päätepiste poistetaan käytöstä tilalla disabled_failures. Pyynnöllä POST …/{id}/enable otat sen taas käyttöön.
webhook-id perusteella. Vastaa nopeasti ja käsittele viesti vasta sen jälkeen.
GET /v1/account/webhook-deliveries näyttää viimeiset 50 toimitusta, parametrilla ?endpoint=wep_… yhden webhook-päätepisteen osalta. GET /v1/account/webhook-events näyttää viimeiset 50 tapahtumaa.
Lopullisesti epäonnistuneen toimituksen lähetät uudelleen pyynnöllä POST /v1/account/webhook-deliveries/{id}/retry, samalla webhook-id:llä ja samalla rungolla. Tämä toimii 30 päivän ajan; niin kauan säilytämme viestit.
| Tila | Merkitys |
|---|---|
| withheld | Odottaa, kunnes tarkistus on veloitettu. |
| pending | Odottaa lähetystä. |
| in_flight | Lähetetään parhaillaan. |
| retry_wait | Odottaa seuraavaa yritystä. |
| succeeded | Toimitettu. |
| exhausted | Kaikki yritykset epäonnistuivat. |
| blocked_endpoint | Webhook-päätepiste ei voi juuri nyt vastaanottaa mitään. |
| cancelled | Peruttu, esimerkiksi koska viesti vanheni. |
Ota käyttöön, poista käytöstä, arkistoi
POST …/{id}/disable pysäyttää toimittamisen toistaiseksi. Monitorit ilman muuta webhook-päätepistettä keskeytyvät silloin tilalla paused_configuration.
POST …/{id}/enable ottaa vahvistetun webhook-päätepisteen taas käyttöön. Jatka sitten keskeytettyjä monitoreja toiminnolla resume.
DELETE …/{id} arkistoi webhook-päätepisteen lopullisesti. Jos haluat pysäyttää sen vain hetkeksi, poista se sen sijaan käytöstä.
curl -X POST "https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/disable" \ -H "Authorization: Bearer SINUN_ADMIN_TOKENISI" \ -H "Idempotency-Key: $(uuidgen)"
import uuid import requests response = requests.post( "https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/disable", headers={ "Authorization": "Bearer SINUN_ADMIN_TOKENISI", "Idempotency-Key": str(uuid.uuid4()), }, ) data = response.json()
const response = await fetch("https://handelsregister.ai/api/v1/account/webhook-endpoints/wep_01m6b3j1g0cdz98hw8zdefbf52/disable", { method: "POST", headers: { "Authorization": "Bearer SINUN_ADMIN_TOKENISI", "Idempotency-Key": crypto.randomUUID(), }, }); const data = await response.json();
Virheet
Virheillä on sama rakenne kuin päätepisteessä account/monitors: koodi kentässä error, selitys kentässä meta.message. Virheet eivät maksa mitään.
| Tila | Syy | Mitä voit tehdä |
|---|---|---|
| 400 |
api_key on URL-osoitteessa. |
Lähetä avain headerissa x-api-key. |
| 400 |
URL ei ole sallittu kohde, header ei ole sallittu, enimmäismäärä on saavutettu tai webhook-päätepiste ei ole aktiivinen. meta.message kertoo syyn. |
Korjaa arvo tai vahvista ensin. |
| 400 |
Idempotency-Key:n muoto on virheellinen. |
Korjaa muoto, esimerkiksi käytä UUID:tä. |
| 401 | API-avain tai token puuttuu tai on virheellinen. |
Lähetä avain headerissa x-api-key. |
| 403 |
Bearer-tokenilta puuttuu oikeus account:read. |
Käytä API-avainta tai tokenia, jolla on account:read. |
| 403 | Tilisi sähköpostiosoitetta ei ole vielä vahvistettu, tai tili on lukittu. | Avaa vahvistussähköpostin linkki. Jos tili on lukittu, tuki voi auttaa. |
| 403 | Tämä kutsu tarvitsee admin-tokenin. API-avain ei riitä, eikä myöskään muu token. |
Lähetä se muodossa Authorization: Bearer admin-tokenin kanssa. |
| 403 |
Bearer-tokenilta puuttuu oikeus monitoring:manage. |
Käytä API-avainta tai admin-tokenia. |
| 404 | Tällä tunnisteella ei ole webhook-päätepistettä tai toimitusta. | Tarkista tunniste. |
| 409 |
Idempotency-Key on jo käytetty eri datalla, on yli 24 tuntia vanha tai on yhä käsittelyssä. |
Käytä uutta avainta tai odota hetki. |
| 422 |
Arvo puuttuu tai on virheellinen, kuten name tai url. errors nimeää kentän. |
Korjaa arvo ja yritä uudelleen uudella avaimella. |
| 422 | Vahvistus epäonnistui. | Tarkista haasteheader ja 2xx-tila ja yritä sitten uudelleen uudella avaimella. |
| 428 |
Header Idempotency-Key puuttuu. |
Lähetä header. |
| 429 | Liian monta pyyntöä. |
Yritä uudelleen ajan Retry-After jälkeen. |
| 503 | Vahvistus, testaus ja uudelleenlähetys eivät ole tilapäisesti käytettävissä. | Yritä hieman myöhemmin uudelleen. |
1{2 "error": "invalid_request",3 "meta": {4 "message": "The endpoint is not active.",5 "request_credit_cost": 0,6 "credits_remaining": 1457 }8}