API Webhooks
Referensi API lengkap untuk mengelola webhooks. Terima notifikasi HTTP real-time saat event terjadi di organisasi PUGUH Anda.
Ringkasan Endpoint
| Method | Endpoint | Deskripsi |
|---|---|---|
GET | /api/v1/webhooks/endpoints | Daftar webhook endpoints |
POST | /api/v1/webhooks/endpoints | Buat webhook endpoint |
GET | /api/v1/webhooks/endpoints/{id} | Detail endpoint |
PATCH | /api/v1/webhooks/endpoints/{id} | Perbarui endpoint |
DELETE | /api/v1/webhooks/endpoints/{id} | Hapus endpoint |
GET | /api/v1/webhooks/events | Daftar tipe event yang tersedia |
GET | /api/v1/webhooks/endpoints/{id}/deliveries | Daftar percobaan pengiriman |
POST | /api/v1/webhooks/deliveries/{id}/retry | Coba ulang pengiriman gagal |
Daftar Webhook Endpoints
GET /api/v1/webhooks/endpoints Parameter Query
| Parameter | Tipe | Default | Deskripsi |
|---|---|---|---|
page | int | 1 | Nomor halaman |
page_size | int | 20 | Hasil per halaman (maks 100) |
is_active | bool | - | Filter berdasarkan status aktif |
Response
{
"items": [
{
"id": "wh-3f8a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"url": "https://example.com/webhooks/puguh",
"description": "Production webhook receiver",
"events": ["user.created", "user.updated", "auth.login"],
"is_active": true,
"created_at": "2026-02-15T08:00:00Z",
"updated_at": "2026-02-18T12:30:00Z"
}
],
"total": 3,
"page": 1,
"page_size": 20,
"has_next": false,
"has_prev": false
} Buat Webhook Endpoint
POST /api/v1/webhooks/endpoints
Content-Type: application/json Request Body
| Field | Tipe | Wajib | Deskripsi |
|---|---|---|---|
url | string | Ya | URL HTTPS yang akan menerima payload webhook |
events | string[] | Ya | Daftar tipe event yang akan di-subscribe |
description | string | Tidak | Deskripsi yang mudah dibaca untuk endpoint ini |
secret | string | Tidak | Secret penandatanganan kustom. Jika tidak diisi, akan dibuat otomatis |
{
"url": "https://example.com/webhooks/puguh",
"events": ["user.created", "user.updated", "organization.updated"],
"description": "Production webhook receiver",
"secret": "whsec_your_custom_secret_key"
} Response 201 Created
{
"id": "wh-3f8a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"url": "https://example.com/webhooks/puguh",
"description": "Production webhook receiver",
"events": ["user.created", "user.updated", "organization.updated"],
"signing_secret": "whsec_your_custom_secret_key",
"is_active": true,
"created_at": "2026-02-20T10:30:00Z",
"updated_at": "2026-02-20T10:30:00Z"
} Penting
signing_secret hanya dikembalikan pada response pembuatan. Simpan dengan aman — Anda akan membutuhkannya untuk memverifikasi tanda tangan webhook. Jika hilang, Anda harus merotasi secret dengan memperbarui endpoint.
Detail Webhook Endpoint
GET /api/v1/webhooks/endpoints/{id} Response
{
"id": "wh-3f8a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"url": "https://example.com/webhooks/puguh",
"description": "Production webhook receiver",
"events": ["user.created", "user.updated", "organization.updated"],
"is_active": true,
"created_at": "2026-02-15T08:00:00Z",
"updated_at": "2026-02-18T12:30:00Z",
"last_delivery_at": "2026-02-20T09:15:00Z",
"delivery_stats": {
"total": 142,
"successful": 139,
"failed": 3
}
} Perbarui Webhook Endpoint
PATCH /api/v1/webhooks/endpoints/{id}
Content-Type: application/json Request Body
Semua field bersifat opsional. Hanya sertakan field yang ingin diubah.
| Field | Tipe | Deskripsi |
|---|---|---|
url | string | URL HTTPS baru |
events | string[] | Ganti tipe event yang di-subscribe |
description | string | Perbarui deskripsi |
secret | string | Rotasi signing secret |
is_active | bool | Aktifkan atau nonaktifkan endpoint |
{
"events": ["user.created", "user.updated", "user.deleted", "auth.login"],
"is_active": true
} Response
{
"id": "wh-3f8a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"url": "https://example.com/webhooks/puguh",
"description": "Production webhook receiver",
"events": ["user.created", "user.updated", "user.deleted", "auth.login"],
"is_active": true,
"created_at": "2026-02-15T08:00:00Z",
"updated_at": "2026-02-20T11:00:00Z"
} Hapus Webhook Endpoint
DELETE /api/v1/webhooks/endpoints/{id} Menghapus webhook endpoint secara permanen dan menghentikan semua pengiriman di masa depan.
Response 204 No Content
Mengembalikan response kosong saat berhasil.
Peringatan
Menghapus webhook endpoint bersifat permanen. Pengiriman yang sedang berlangsung akan dibatalkan. Jika Anda ingin menghentikan penerimaan event sementara, gunakan PATCH untuk mengatur is_active ke false.
Daftar Tipe Event
GET /api/v1/webhooks/events Mengembalikan semua tipe event yang tersedia untuk subscription webhook.
Response
[
{
"type": "user.created",
"category": "user",
"description": "A new user has been created"
},
{
"type": "user.updated",
"category": "user",
"description": "A user profile has been updated"
},
{
"type": "organization.created",
"category": "organization",
"description": "A new organization has been created"
}
] Tipe Event yang Tersedia
| Kategori | Tipe Event | Deskripsi |
|---|---|---|
| User | user.created | Pengguna baru telah dibuat |
| User | user.updated | Profil pengguna telah diperbarui |
| User | user.deleted | Pengguna telah dihapus |
| Organization | organization.created | Organisasi baru telah dibuat |
| Organization | organization.updated | Organisasi telah diperbarui |
| Member | member.invited | Pengguna telah diundang ke organisasi |
| Member | member.joined | Pengguna telah bergabung ke organisasi |
| Member | member.removed | Anggota telah dihapus dari organisasi |
| Application | application.created | Aplikasi baru telah dibuat |
| Application | application.updated | Aplikasi telah diperbarui |
| Application | application.deleted | Aplikasi telah dihapus |
| Auth | auth.login | Pengguna telah login |
| Auth | auth.logout | Pengguna telah logout |
| Auth | auth.password_changed | Pengguna telah mengubah password |
| Billing | billing.subscription.created | Subscription baru telah dibuat |
| Billing | billing.invoice.paid | Invoice telah dibayar |
Daftar Pengiriman
GET /api/v1/webhooks/endpoints/{id}/deliveries Mengembalikan percobaan pengiriman untuk webhook endpoint tertentu.
Parameter Query
| Parameter | Tipe | Default | Deskripsi |
|---|---|---|---|
page | int | 1 | Nomor halaman |
page_size | int | 20 | Hasil per halaman (maks 100) |
status | string | - | Filter berdasarkan status: success, failed, pending |
event | string | - | Filter berdasarkan tipe event (contoh user.created) |
Response
{
"items": [
{
"id": "del-a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"webhook_id": "wh-3f8a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"event": "user.created",
"status": "success",
"http_status": 200,
"attempt": 1,
"max_attempts": 5,
"request_body": {
"event": "user.created",
"timestamp": "2026-02-20T10:30:00Z",
"data": {
"id": "usr-uuid",
"email": "newuser@example.com"
},
"webhook_id": "wh-3f8a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"delivery_id": "del-a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d"
},
"response_body": "OK",
"duration_ms": 120,
"delivered_at": "2026-02-20T10:30:01Z",
"next_retry_at": null
},
{
"id": "del-b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"webhook_id": "wh-3f8a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"event": "auth.login",
"status": "failed",
"http_status": 500,
"attempt": 3,
"max_attempts": 5,
"response_body": "Internal Server Error",
"duration_ms": 5032,
"delivered_at": "2026-02-20T09:15:05Z",
"next_retry_at": "2026-02-20T10:15:05Z"
}
],
"total": 142,
"page": 1,
"page_size": 20,
"has_next": true,
"has_prev": false
} Coba Ulang Pengiriman Gagal
POST /api/v1/webhooks/deliveries/{id}/retry Mencoba ulang pengiriman webhook yang gagal secara manual. Pengiriman langsung di-queue ulang tanpa memperhatikan jadwal retry.
Response
{
"id": "del-b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"status": "pending",
"attempt": 4,
"max_attempts": 5,
"next_retry_at": null,
"queued_at": "2026-02-20T11:00:00Z"
} Kebijakan Retry
PUGUH menggunakan exponential backoff untuk retry otomatis: 1 menit, 5 menit, 30 menit, 2 jam, 24 jam. Setelah 5 percobaan gagal, pengiriman ditandai sebagai gagal permanen. Gunakan endpoint ini untuk mencoba ulang secara manual kapan saja.
Format Payload Webhook
Setiap pengiriman webhook mengirim payload JSON dengan struktur yang konsisten:
{
"event": "user.created",
"timestamp": "2026-02-20T10:30:00Z",
"data": {
"id": "usr-a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"email": "newuser@example.com",
"full_name": "Jane Doe",
"created_at": "2026-02-20T10:30:00Z"
},
"webhook_id": "wh-3f8a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"delivery_id": "del-a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d"
} Field Payload
| Field | Tipe | Deskripsi |
|---|---|---|
event | string | Tipe event yang memicu pengiriman ini |
timestamp | string | Timestamp ISO 8601 kapan event terjadi |
data | object | Data payload spesifik event (bervariasi per tipe event) |
webhook_id | string | ID webhook endpoint yang menerima pengiriman ini |
delivery_id | string | Identifier unik untuk percobaan pengiriman spesifik ini |
Header HTTP yang Dikirim dengan Pengiriman
| Header | Deskripsi |
|---|---|
Content-Type | application/json |
X-Puguh-Signature | Tanda tangan HMAC-SHA256 untuk verifikasi payload |
X-Puguh-Event | Tipe event (contoh user.created) |
X-Puguh-Delivery-ID | Identifier pengiriman unik |
X-Puguh-Timestamp | Timestamp Unix pengiriman untuk perlindungan replay |
User-Agent | PUGUH-Webhooks/1.0 |
Verifikasi Tanda Tangan
Setiap pengiriman webhook menyertakan header X-Puguh-Signature yang berisi tanda tangan HMAC-SHA256. Anda harus memverifikasi tanda tangan ini untuk memastikan request berasal dari PUGUH dan tidak dimanipulasi.
Cara Kerja Tanda Tangan
- PUGUH menggabungkan timestamp pengiriman dan body JSON mentah:
timestamp.body - PUGUH menghitung HMAC-SHA256 menggunakan signing secret endpoint Anda sebagai key
- Tanda tangan dikirim di header
X-Puguh-Signaturesebagaisha256=HEX_DIGEST - Server Anda harus menghitung ulang tanda tangan dan membandingkan menggunakan perbandingan constant-time
Contoh Verifikasi Python
import hmac
import hashlib
import time
def verify_webhook_signature(
payload: bytes,
signature_header: str,
timestamp_header: str,
secret: str,
tolerance_seconds: int = 300
) -> bool:
"""Verify PUGUH webhook signature.
Args:
payload: Raw request body bytes.
signature_header: Value of X-Puguh-Signature header.
timestamp_header: Value of X-Puguh-Timestamp header.
secret: Your webhook endpoint signing secret.
tolerance_seconds: Max age in seconds (default 5 min).
Returns:
True if signature is valid and timestamp is within tolerance.
"""
# 1. Check timestamp to prevent replay attacks
try:
timestamp = int(timestamp_header)
except (ValueError, TypeError):
return False
if abs(time.time() - timestamp) > tolerance_seconds:
return False
# 2. Compute expected signature
signed_content = f"{timestamp}.".encode() + payload
expected = hmac.new(
secret.encode(),
signed_content,
hashlib.sha256
).hexdigest()
# 3. Compare using constant-time comparison
expected_sig = f"sha256={expected}"
return hmac.compare_digest(expected_sig, signature_header)
# Usage in a Flask/FastAPI handler
from fastapi import Request, HTTPException
WEBHOOK_SECRET = "whsec_your_signing_secret"
@app.post("/webhooks/puguh")
async def handle_webhook(request: Request):
payload = await request.body()
signature = request.headers.get("X-Puguh-Signature", "")
timestamp = request.headers.get("X-Puguh-Timestamp", "")
if not verify_webhook_signature(payload, signature, timestamp, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
event = request.headers.get("X-Puguh-Event")
data = await request.json()
if event == "user.created":
handle_user_created(data["data"])
elif event == "billing.invoice.paid":
handle_invoice_paid(data["data"])
return {"received": True} Contoh Verifikasi TypeScript
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhookSignature(
payload: string,
signatureHeader: string,
timestampHeader: string,
secret: string,
toleranceSeconds: number = 300
): boolean {
// 1. Check timestamp to prevent replay attacks
const timestamp = parseInt(timestampHeader, 10);
if (isNaN(timestamp)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > toleranceSeconds) return false;
// 2. Compute expected signature
const signedContent = `${timestamp}.${payload}`;
const expectedHex = createHmac("sha256", secret)
.update(signedContent)
.digest("hex");
const expectedSig = `sha256=${expectedHex}`;
// 3. Compare using constant-time comparison
if (expectedSig.length !== signatureHeader.length) return false;
return timingSafeEqual(
Buffer.from(expectedSig),
Buffer.from(signatureHeader)
);
}
// Usage in an Express handler
import express from "express";
const WEBHOOK_SECRET = process.env.PUGUH_WEBHOOK_SECRET!;
app.post(
"/webhooks/puguh",
express.raw({ type: "application/json" }),
(req, res) => {
const payload = req.body.toString();
const signature = req.headers["x-puguh-signature"] as string;
const timestamp = req.headers["x-puguh-timestamp"] as string;
if (!verifyWebhookSignature(payload, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).json({ detail: "Invalid signature" });
}
const event = req.headers["x-puguh-event"] as string;
const data = JSON.parse(payload);
switch (event) {
case "user.created":
handleUserCreated(data.data);
break;
case "billing.invoice.paid":
handleInvoicePaid(data.data);
break;
}
res.json({ received: true });
}
); Tip Keamanan
Selalu verifikasi header X-Puguh-Timestamp untuk mencegah serangan replay. Tolak pengiriman yang lebih dari 5 menit. Selalu gunakan perbandingan constant-time (contoh hmac.compare_digest di Python, timingSafeEqual di Node.js) untuk mencegah serangan timing.
Praktik Terbaik
Respons Cepat
Webhook endpoint Anda harus mengembalikan kode status 2xx dalam 10 detik. Jika pemrosesan membutuhkan waktu lebih lama, terima webhook segera dan proses event secara asinkron menggunakan antrian background job.
Tangani Pengiriman Duplikat
Gunakan field delivery_id untuk deduplikasi event. Dalam kasus langka (timeout jaringan, retry), event yang sama mungkin dikirim lebih dari sekali. Simpan delivery ID yang sudah diproses dan lewati duplikat.
Gunakan Endpoint HTTPS
URL webhook harus menggunakan HTTPS. PUGUH akan menolak endpoint yang dikonfigurasi dengan URL HTTP.
Pantau Kesehatan Pengiriman
Gunakan endpoint Daftar Pengiriman untuk memantau tingkat kegagalan. Jika endpoint secara konsisten gagal, PUGUH akan otomatis menonaktifkannya setelah 5 kegagalan berturut-turut dan mengirim notifikasi ke owner organisasi.
Response Error
Semua response error menggunakan kode status HTTP dengan pesan detail biasa. Tanpa objek wrapper.
400 Bad Request
{
"detail": "Invalid URL: must be HTTPS"
} 404 Not Found
{
"detail": "Webhook endpoint not found"
} 409 Conflict
{
"detail": "Delivery is already in pending state"
} 422 Unprocessable Entity
{
"detail": "Invalid event type: user.unknown. Use GET /webhooks/events for available types."
} Terkait
- API Autentikasi — Cara mengautentikasi request API
- API Organizations — Kelola organisasi dan anggota
- Python SDK — Gunakan webhooks melalui Python SDK
- TypeScript SDK — Gunakan webhooks melalui TypeScript SDK