Pembaruan Rust 1.97 jadi momen yang tepat untuk menetapkan kontrak API webhook yang lebih kuat. Artikel ini langsung menjawab: bagaimana merancang klausa idempotensi, pola retry berbasis status, serta verifikasi signature/token agar penerima bisa menangani duplikasi dan kesalahan serialisasi dengan aman.

Penjelasan berikut fokus pada struktur payload, enum status retry, pendekatan async Rust, dan langkah-langkah praktis untuk menjaga konsistensi antar sistem.

Desain Kontrak Webhook dengan Fokus Idempotensi

Kontrak harus mengharuskan setiap payload membawa event_id unik, timestamp, tipe peristiwa, dan data terstruktur. Klausa idempotensi menetapkan bahwa penerima wajib menyimpan event_id yang sudah diproses sebelum mengeksekusi logika bisnis, sehingga pengiriman ulang tidak menghasilkan efek ganda.

Struktur payload yang bisa dijadikan standar:

use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};

#[derive(Deserialize, Serialize)]
struct WebhookPayload {
    event_id: Uuid,
    event_type: String,
    timestamp: DateTime,
    data: T,
}

Bagian event_id harus divalidasi terhadap cache atau basis data idempotensi. Bila nilai sudah tercatat, penerima bisa langsung membalas 200 OK tanpa memproses ulang.

Juga tetapkan batas waktu logis (misalnya 24 jam) agar idempotensi tidak memerlukan penyimpanan permanen.

Status Retry Berbasis Respon dan Enum Rust

Kontrak perlu menyertakan enum status retry yang mengomunikasikan apakah webhook perlu dikirim ulang. Contoh enum:

enum RetryStatus {
    Delivered,
    RetryableTemporary { delay_seconds: u64 },
    RetryablePermanent { reason: &'static str },
}

Pada sisi pengirim, tiga status ini bisa diterjemahkan ke kode HTTP: 2xx untuk Delivered, 5xx atau 429 untuk RetryableTemporary, dan 4xx spesifik (misal 403) untuk RetryablePermanent. Kontrak menjelaskan bahwa status temporary harus di-retry dengan backoff, sementara permanent menandakan instruksi untuk tidak mengulang.

Di sisi penerima, simpan alasan kegagalan agar sistem pengirim bisa memetakan enum secara konsisten.

Validasi Signature dan Auth Token di Penerima

Penerima harus mengandalkan kombinasi header khusus untuk memverifikasi bahwa payload berasal dari pengirim sah. Misalnya:

  • X-Signature: HMAC-SHA256 terhadap payload menggunakan secret bersama.
  • Authorization: Bearer <token>: token statis/rotating untuk otorisasi.

Prosedurnya:

  1. Hitung HMAC dari badan request sebelum parsing. Bila tidak cocok dengan header signature, langsung balas 401 Unauthorized.
  2. Validasi token; bila token kadaluarsa atau tidak dikenali, balas 403 Forbidden.
  3. Setelah validasi, parse JSON. Bila serialisasi gagal, berikan 400 Bad Request dan catat error untuk debugging.

Contoh pemeriksaan HMAC:

let signature = req.headers().get("X-Signature").and_then(|v| v.to_str().ok());
let computed = hmac_sha256(secret, &body);
if signature != Some(&computed) {
    return Response::builder().status(401).body(Body::from("invalid signature"));
}

Dengan kontrak seperti ini, pengirim dan penerima sama-sama mengetahui kapan signature dan token wajib diperiksa.

Pendekatan Async Rust untuk Hindari Duplikasi dan Serialisasi Error

Rust async sangat berguna untuk memproses webhook secara paralel sambil menjaga atomisitas idempotensi. Contoh pola processing:

async fn handle_webhook(body: bytes::Bytes) -> Result {
    let payload: WebhookPayload = serde_json::from_slice(&body)
        .map_err(WebhookError::Deserialization)?;
    if let Some(_) = check_event_processed(payload.event_id).await {
        return Ok(RetryStatus::Delivered);
    }
    match process_business(payload).await {
        Ok(_) => {
            mark_event_processed(payload.event_id).await;
            Ok(RetryStatus::Delivered)
        }
        Err(e) => {
            if e.is_transient() {
                Err(WebhookError::Transient(e))
            } else {
                Err(WebhookError::Permanent(e))
            }
        }
    }
}

Gunakan crate async (Tokio/async-std) sesuai runtime proyek, tanpa bergantung pada fitur rarified di Rust 1.97. Penting untuk menunggu operasi I/O seperti cek idempotensi dan penulisan log sebelum mengirim balasan.

Jika parsing JSON gagal, struktur error harus menyertakan informasi yang bisa digunakan dalam log operator tanpa membocorkan data sensitif. Hindari mengulang proses yang sama bila data tidak valid.

Checklist Operasional Tim Integrasi API

  • Dokumentasikan payload schema lengkap dan sertakan contoh event_id yang valid.
  • Tentukan kode HTTP yang dikaitkan dengan RetryStatus di kontrak.
  • Terapkan validasi HMAC/Authorization di titik masuk; log signature mismatch dan token issues.
  • Simpan event_id untuk idempotensi dan jalankan garbage collection setelah jangka waktu yang disepakati.
  • Pastikan penerima memberi respons tepat waktu agar pengirim dapat mengelola retry.
  • Tambahkan observabilitas (log, metrics) untuk retry, serialisasi error, dan status signature.
  • Latih tim operasional dengan skenario failure untuk memastikan seluruh kontrak berjalan konsisten.