# Webhook

Pemilik workspace menambahkan endpoint di **Settings → Integrations → Webhooks**, atau lewat [API](/api/operations/createwebhook/)
(maksimal 5), memilih event-nya, dan bisa mengirim tes.

| Event | Kapan | Payload |
|---|---|---|
| `post.published` | Post satu akun sudah tayang, beserta link-nya | [skema](/api/webhooks/postpublished/) |
| `post.failed` | Satu akun gagal setelah percobaan ulang habis, beserta alasannya | [skema](/api/webhooks/postfailed/) |
| `post.handoff` | Video TikTok menunggu di inbox TikTok akun tersebut | [skema](/api/webhooks/posthandoff/) |
| `comment.received` | Komentar baru (atau balasan di thread yang menunggu Anda) di Facebook atau Instagram | [skema](/api/webhooks/commentreceived/) |
| `message.received` | Pesan langsung baru di Facebook atau Instagram | [skema](/api/webhooks/messagereceived/) |
| `account.needs_reconnect` | Akun kehilangan akses; post ke akun itu gagal sampai disambung ulang | [skema](/api/webhooks/accountneedsreconnect/) |
| `ping` | Tombol tes, atau [`POST /webhooks/{id}/test`](/api/operations/testwebhook/) | [skema](/api/webhooks/webhooktest/) |

Event post dikirim per akun: satu post ke tiga akun mengirim sampai tiga `post.published`.

## Payload

Setiap pengiriman berisi `event`, `deliveredAt` dan `workspaceId`. Event post:

```json
{
  "event": "post.published",
  "deliveredAt": "2026-11-03T02:00:41.512Z",
  "workspaceId": "ws_…",
  "post": { "id": "p_…", "caption": "Es kopi gula aren…", "scheduledAt": "2026-11-03T02:00:00.000Z" },
  "target": {
    "id": "t_…",
    "platform": "instagram",
    "account": { "name": "Kopi Senja", "username": "kopisenja" },
    "contentType": "ig_image",
    "status": "published",
    "permalink": "https://www.instagram.com/p/…",
    "externalPostId": "1789…",
    "publishedAt": "2026-11-03T02:00:39.000Z",
    "error": null
  }
}
```

Event komentar:

```json
{
  "event": "comment.received",
  "deliveredAt": "2026-11-03T05:12:00.000Z",
  "workspaceId": "ws_…",
  "account": { "id": "acc_ig", "platform": "instagram", "name": "Kopi Senja", "username": "kopisenja" },
  "comment": { "id": "cm_…", "isReply": false, "authorName": "dewi.s", "text": "Buka sampai jam berapa?", "commentedAt": "2026-11-03T05:09:41.000Z" }
}
```

Balas dengan [`POST /inbox/{comment.id}/reply`](/api/operations/replytocomment/). `message.received` membawa `conversation`
(balas dengan [`POST /inbox/messages/{id}/reply`](/api/operations/replytoconversation/)) dan `messages` yang baru;
`account.needs_reconnect` membawa `account` dan `reason`.

Header: `Content-Type: application/json`, `User-Agent: PostFlow-Webhooks/1`, `X-PostFlow-Event`, dan `X-PostFlow-Signature`.

## Pengiriman

Jawab dengan `2xx` apa pun dalam 10 detik. Jawaban lain, timeout dan redirect dihitung gagal dan dicoba ulang dengan jeda yang makin
panjang (dua kali lipat setiap kali, sampai jarak satu jam), maksimal 5 kali lagi. Hasil terakhir tampil di samping webhook di Settings.

## Memverifikasi tanda tangan

`X-PostFlow-Signature: t=<detik unix>,v1=<hex>` dengan `v1` adalah HMAC-SHA256 dari `"<t>.<body mentah>"` memakai signing secret
webhook (tampil di Settings). Bandingkan dengan constant time dan tolak timestamp yang sudah lama.

```js
import { createHmac, timingSafeEqual } from 'node:crypto'

export function verify(rawBody, header, secret, toleranceSec = 300) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}
```

```python
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f'{parts["t"]}.'.encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(parts.get("v1", ""), expected)
```

```php
function verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
    parse_str(str_replace(',', '&', $header), $p);
    if (abs(time() - (int) $p['t']) > $tolerance) return false;
    $expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);
    return hash_equals($expected, $p['v1'] ?? '');
}
```

Pakai body request mentah: mengurai lalu menyusun ulang JSON mengubah byte-nya dan merusak tanda tangan.

## Mengelola endpoint lewat API

Key milik pemilik bisa [melihat daftar](/api/operations/listwebhooks/), [membuat](/api/operations/createwebhook/),
[mengubah](/api/operations/updatewebhook/) (termasuk `active: false` untuk menjeda), [menghapus](/api/operations/deletewebhook/) dan
[mengetes](/api/operations/testwebhook/) endpoint, serta [merotasi signing secret](/api/operations/rotatewebhooksecret/). Setelah
dirotasi, pengiriman langsung ditandatangani dengan secret baru: buat receiver Anda menerima secret lama dan baru sampai diperbarui.