> ## Documentation Index
> Fetch the complete documentation index at: https://www.twicecommerce.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhookit

> Miten TWICE Commerce toimittaa allekirjoitetut tilaustapahtumailmoitukset päätepisteeseesi HTTP POST -pyyntönä.

<Frame caption="Asetukset > Integraatiot ja API">
  <img src="https://mintcdn.com/twicecommerce/lZhc_tO8u1u_bL0Q/images/settings-integrations.webp?fit=max&auto=format&n=lZhc_tO8u1u_bL0Q&q=85&s=40fdc769db7318c16cae719abf0e7dd0" alt="Webhookien määritys integraatioasetuksissa" width="1920" height="1080" data-path="images/settings-integrations.webp" />
</Frame>

<Frame caption="Asetukset > Integraatiot ja API > Webhookit">
  <img src="https://mintcdn.com/twicecommerce/yRIgvb0PY9wrSJi9/images/settings-webhooks-table.webp?fit=max&auto=format&n=yRIgvb0PY9wrSJi9&q=85&s=99114fe3ea23f1406dea39e426f5ea56" alt="Webhook-taulukko, jossa tapahtuma, URL, status, luontipäivä ja rivin toimintovalikko auki" width="1920" height="1080" data-path="images/settings-webhooks-table.webp" />
</Frame>

Webhookit ilmoittavat järjestelmällesi, kun tilaus muuttuu TWICE Commercessa. Kun tapahtuma laukeaa, TWICE lähettää HTTP POST -pyynnön rekisteröimääsi URL-osoitteeseen, joten voit reagoida ilman API:n kyselyä.

## Tapahtumatyypit

TWICE lähettää kolme tilaustapahtumaa:

| Tapahtuma       | Milloin se laukeaa                    |
| :-------------- | :------------------------------------ |
| `order.created` | Uusi tilaus luodaan                   |
| `order.updated` | Tilaus tai jokin sen alatieto muuttuu |
| `order.deleted` | Tilaus poistetaan                     |

Hyötykuorman `data` sisältää kyseessä olevan tilauksen. Resursseja ja tapahtumia voidaan lisätä myöhemmin.

### Mikä laukaisee tapahtuman `order.updated`

`order.updated` laukeaa, kun tilaus itse muuttuu **ja** kun jokin sen alatieto muuttuu:

* Rivitiedot
* Varastoartikkelit
* Asiakkaat
* Kommentit
* Tunnisteet
* Dokumentit
* Alennukset
* Attribuutit
* Kassan kentät

Maksutietueet eivät kuulu tähän joukkoon — maksun kirjoittaminen ei itsessään laukaise tapahtumaa `order.updated`.

Yksi operaatio, joka koskee useaa alariviä (esimerkiksi joukkomuokkaus, joka päivittää tilauksen ja useita rivitietoja samassa transaktiossa), tuottaa **yhden** toimituksen, ei montaa. TWICE yhdistää nopeasti peräkkäiset muutokset tilauskohtaisesti, joten päätepisteesi saa yhden koostetun tapahtuman.

<Note>
  Odottavien tilausten (kesken olevien kassaluonnosten) vaimennus koskee vain luontia: odottavan tilauksen tallentaminen ei lähetä mitään, ja `order.created` laukeaa, kun tilaus poistuu odottavasta statuksesta. Odottavan tilauksen päivitykset lähettävät silti tapahtuman `order.updated`, ja poistaminen lähettää tapahtuman `order.deleted`.
</Note>

## Miten se toimii

Jokainen toimitus on HTTP `POST`, jonka JSON-runko on tapahtumaa kuvaava kuori. Kyseessä oleva resurssi kulkee `data`-kentässä:

```json theme={null}
{
  "eventId": "5f2eb3a6-6cbd-4a2e-9350-6e0d1e2f9a11",
  "eventType": "order.created",
  "eventTime": "2026-08-11T12:34:56.789Z",
  "resource": "order",
  "resourceId": "0d9e0a7c-3fd1-4c8f-9c0d-2b7c6f7f2a55",
  "apiVersion": "2025-06",
  "data": { "id": "0d9e0a7c-3fd1-4c8f-9c0d-2b7c6f7f2a55", "…": "the full Order object" }
}
```

<ResponseField name="eventId" type="string">
  Tapahtuman yksilöivä tunnus. Lähetetään myös `X-Twice-Event-Id` -otsakkeessa. Tee kaksoiskappaleiden karsinta tämän arvon perusteella.
</ResponseField>

<ResponseField name="eventType" type="string">
  Laukennut tapahtuma, esimerkiksi `order.created`. Yksi webhook vastaanottaa yhtä tapahtumatyyppiä.
</ResponseField>

<ResponseField name="eventTime" type="string">
  ISO 8601 -aikaleima siitä, milloin tapahtuma sattui.
</ResponseField>

<ResponseField name="resource" type="string">
  Resurssi, jota tapahtuma koskee, esimerkiksi `order`.
</ResponseField>

<ResponseField name="resourceId" type="string">
  Kyseessä olevan resurssin tunnus.
</ResponseField>

<ResponseField name="apiVersion" type="string">
  API-versio, jonka mukaisena hyötykuorma muodostetaan.
</ResponseField>

<ResponseField name="data" type="object | null">
  Resurssi samassa muodossa kuin REST API sen palauttaa. `null` tapahtumalle `order.deleted`.
</ResponseField>

`data`-kentän sisältämä Order-skeema on kuvattu [API-viitteessä](https://server.twicecommerce.com/api/internal).

### API-version kiinnitys

Jokainen webhook on kiinnitetty siihen API-versioon, joka oli uusin sen luontihetkellä, ja versio ilmoitetaan `apiVersion`-kentässä. Uusi API-versio ei koskaan muuta jo vastaanottamaasi hyötykuorman muotoa — webhookin siirtäminen uudempaan versioon on erillinen päivitys.

## Webhookien hallinta

Hallitse webhookeja kohdassa **Asetukset → Integraatiot ja API → Webhookit**. Luo webhook valitsemalla tapahtumatyyppi ja syöttämällä päätepisteen URL, joka vastaanottaa POST-pyynnön. Kohde-URL-osoitteiden on oltava **HTTPS**-osoitteita.

Taulukossa näkyy kustakin webhookista:

| Sarake         | Kuvaus                                                                         |
| :------------- | :----------------------------------------------------------------------------- |
| **Tapahtuma**  | Yksittäinen tapahtumatyyppi, jonka tämä webhook vastaanottaa                   |
| **URL-osoite** | HTTPS-päätepiste, johon hyötykuorma lähetetään                                 |
| **Tila**       | Aktiivinen tai Poistettu käytöstä — syy näkyy, kun viet kursorin merkin päälle |
| **Luotu**      | Milloin webhook luotiin                                                        |

Loput löytyvät rivin toiminnoista: **Näytä toimitukset**, **Ota käyttöön** / **Poista käytöstä**, **Näytä allekirjoitusavain** ja **Poista**.

Sama tapahtuma voidaan toimittaa useaan webhookiin — esimerkiksi yksi `order.created`-webhook CRM-järjestelmääsi ja toinen kirjanpitojärjestelmääsi.

<Note>
  Webhookit edellyttävät niitä sisältävää pakettia — muut tilit näkevät päivityskehotteen integraatioasetuksissa. Katso [paketit](/docs/fi/twice-commerce-overview).
</Note>

## Toimitus ja uudelleenyritykset

TWICE toimittaa webhookit asynkronisesti Cloud Tasks -jonon kautta. Toimitus onnistuu millä tahansa `2xx`-vastauksella; mikä tahansa muu status — tai aikakatkaisu — lasketaan epäonnistuneeksi yritykseksi.

Epäonnistunut toimitus yritetään uudelleen enintään 3 kertaa (yhteensä 4 yritystä), noin 10 s, 20 s ja 40 s edellisen yrityksen jälkeen, ja uudelleenyritykset päättyvät 10 minuuttia ensimmäisen yrityksen jälkeen.

Kun **3 peräkkäistä tapahtumaa** on käyttänyt kaikki yrityksensä, webhook poistetaan automaattisesti käytöstä syyllä `TOO_MANY_FAILED_DELIVERY_ATTEMPTS`, ja otat sen takaisin käyttöön käsin, kun päätepisteesi on jälleen tavoitettavissa. Käyttöönotto — tai webhookin osoittaminen toiseen URL-osoitteeseen — aloittaa uuden virheikkunan.

Vastaa `2xx` nopeasti. Jos käsittely on hidasta, kuittaa ensin ja siirrä työ jonoon.

## Toimitusloki

Jokainen lähetetty toimitus tallennetaan. Näet ne webhookin **Näytä toimitukset** -toiminnolla osoitteessa `/settings/connect/integrations/webhooks/{webhookId}`. Lokia voi suodattaa ja järjestää tuloksen, tapahtuman, tapahtumatunnuksen, laukaisuajan, yritysten määrän ja viimeisimmän vastauksen mukaan.

<Frame caption="Toimituksen tiedot — yritysten aikajana ja allekirjoitettu hyötykuorma">
  <img src="https://mintcdn.com/twicecommerce/xUE4mkDkj1E022kJ/images/webhook-delivery-log.webp?fit=max&auto=format&n=xUE4mkDkj1E022kJ&q=85&s=262c4ac0b7e8b7b42879bd1208e53fad" alt="Toimitusloki, jossa toimituksen tietopaneeli auki näyttäen yritysten aikajanan ja hyötykuorman" width="1920" height="1080" data-path="images/webhook-delivery-log.webp" />
</Frame>

Toimituksella on yksi neljästä statuksesta:

| Status                  | Merkitys                                              |
| :---------------------- | :---------------------------------------------------- |
| **Odottaa**             | Jonossa, yrityksen lopputulosta ei ole vielä kirjattu |
| **Yritetään uudelleen** | Yritys epäonnistui ja seuraava on ajastettu           |
| **Toimitettu**          | Yritys palautti `2xx`                                 |
| **Epäonnistui**         | Kaikki yritykset epäonnistuivat                       |

Avaamalla rivin näet toimituksen tiedot: tapahtumatunnuksen, laukaisuajan, yritysten määrän ja viimeisimmän vastauksen, yritysten aikajanan kunkin yrityksen lopputuloksineen sekä täsmällisen lähetetyn hyötykuorman. Sieltä voit valita **Kopioi payload** tai **Lähetä uudelleen**, joka lähettää saman tallennetun hyötykuorman kohde-URL-osoitteeseen uudelleen.

<Note>
  Toimitukset, jotka on kirjattu ennen kuin toimitusloki tallensi hyötykuormia, eivät näytä hyötykuormaa eikä niitä voi lähettää uudelleen.
</Note>

Testitoimitusta ei ole. Jos haluat kokeilla päätepistettä ennen tuotantoliikenteen ohjaamista siihen, luo webhook väliaikaiseen URL-osoitteeseen (luontidialogi ehdottaa palvelua [webhook.site](https://webhook.site)) ja laukaise tapahtuma.

## Kaksoiskappaleiden karsinta

Sama tapahtuma voi saapua useammin kuin kerran, joten päätepisteesi on oltava **idempotentti**. Karsi kaksoiskappaleet `eventId`-arvon perusteella — se lähetetään sekä rungossa että `X-Twice-Event-Id` -otsakkeessa.

Myöskään järjestystä ei taata. Kun järjestyksellä on merkitystä, vertaa `eventTime`-arvoja tai hae resurssin nykytila uudelleen API:n kautta.

## Allekirjoitusten varmennus

Jokaisella webhookilla on allekirjoitusavain, jonka etuliite on `whsec_`. TWICE allekirjoittaa sillä jokaisen toimituksen ja lähettää allekirjoituksen `X-Twice-Signature` -otsakkeessa muodossa `sha256=<hex>` — se on HMAC-SHA256 täsmällisestä raa'asta pyyntörungosta.

Varmenna raakojen runkotavujen perusteella ja jäsennä JSON vasta, kun allekirjoitus on todettu oikeaksi:

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  import express from 'express';
  import { createHmac, timingSafeEqual } from 'node:crypto';

  const app = express();
  const secret = process.env.TWICE_WEBHOOK_SECRET;

  // Raw body — the signature covers the exact bytes TWICE sent
  app.post('/webhooks/order-created', express.raw({ type: 'application/json' }), (req, res) => {
    const expected = 'sha256=' + createHmac('sha256', secret).update(req.body).digest('hex');
    const signature = req.get('X-Twice-Signature');
    const isValid =
      typeof signature === 'string' &&
      signature.length === expected.length &&
      timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

    if (!isValid) return res.status(401).send('Invalid signature');

    const event = JSON.parse(req.body.toString('utf8'));
    handleOrderCreated(event.eventId, event.data);
    res.status(200).send('OK');
  });

  app.listen(3000);
  ```

  ```python Python (Flask) theme={null}
  import hmac, hashlib, json, os
  from flask import Flask, request

  app = Flask(__name__)
  secret = os.environ['TWICE_WEBHOOK_SECRET']

  @app.post('/webhooks/order-created')
  def order_created():
      raw_body = request.get_data()
      expected = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
      signature = request.headers.get('X-Twice-Signature', '')

      if not hmac.compare_digest(signature, expected):
          return ('Invalid signature', 401)

      event = json.loads(raw_body)
      handle_order_created(event['eventId'], event['data'])
      return ('OK', 200)
  ```
</CodeGroup>

### Allekirjoitusavaimen hallinta

Webhookin **Näytä allekirjoitusavain** -toiminnolla näytät, kopioit tai kierrätät avaimen.

<Warning>
  Kierrättäminen korvaa avaimen välittömästi — toimitukset allekirjoitetaan siitä hetkestä alkaen uudella avaimella, joten päivitä päätepisteesi samaan aikaan.
</Warning>

Ennen allekirjoitusten käyttöönottoa luoduilla webhookeilla ei ole allekirjoitusavainta, ja niiden toimitukset pysyvät **allekirjoittamattomina**, kunnes luot sellaisen. Dialogi tarjoaa niille toiminnon **Luo avain**.

## Kehittäjän viitetiedot

Webhookeja hallitaan `hooks`-päätepisteillä.

<Card title="API: Webhooks" icon="code" href="https://server.twicecommerce.com/api/internal">
  Avaa päätepiste API-viitteessä.
</Card>

## Aiheeseen liittyvät

<CardGroup cols={2}>
  <Card title="Integraatiot" icon="plug" href="/docs/fi/concepts/integrations/overview">
    Webhookit API-avainten rinnalla.
  </Card>

  <Card title="API-avaimet" icon="key" href="/docs/fi/concepts/integrations/api-keys">
    Tunnistaudu API:iin.
  </Card>

  <Card title="Tapahtumalokit" icon="clock-rotate-left" href="/docs/fi/concepts/admin/activity-logs">
    Jäljitysketju, jota vasten voit täsmäyttää.
  </Card>

  <Card title="Tilauksen elinkaari" icon="rotate" href="/docs/fi/concepts/orders/order-lifecycle">
    Milloin tilaustapahtumat laukeavat.
  </Card>
</CardGroup>
