Tutorial 23 Aug 2026 1 Kali Dibaca

Cara Membuat Webhook Laravel yang Aman dengan Signature Verification

Gugun Nurdiansyah
Penulis
Cara Membuat Webhook Laravel yang Aman dengan Signature Verification

Apa Itu Webhook?

Webhook adalah mekanisme ketika sebuah layanan mengirimkan HTTP request ke aplikasi setelah terjadi suatu event.

Contohnya:

Pembayaran berhasil
↓
Payment Gateway
↓
Webhook Laravel
↓
Update Status Transaksi

Webhook banyak digunakan untuk:

  1. Payment gateway.

  2. WhatsApp API.

  3. Marketplace.

  4. Sistem ERP.

  5. Sistem CRM.

  6. Aplikasi pihak ketiga.

Mengapa Webhook Harus Diamankan?

Endpoint webhook biasanya dapat diakses melalui internet.

Misalnya:

POST /api/webhook/payment

Jika tidak memiliki validasi, siapa pun dapat mencoba mengirim request:

{
    "status": "paid"
}

Aplikasi bisa saja menganggap pembayaran benar-benar berhasil.

Karena itu, webhook perlu melakukan verifikasi terhadap sumber request.

Apa Itu Signature Verification?

Signature verification menggunakan secret key yang hanya diketahui oleh aplikasi dan penyedia webhook.

Konsep sederhananya:

Provider
↓
Data + Secret Key
↓
Generate Signature
↓
Laravel menerima request
↓
Generate Signature kembali
↓
Bandingkan
↓
Valid / Ditolak

Jika signature tidak cocok, request ditolak.

1. Membuat Endpoint Webhook

Tambahkan route:

Route::post(
    '/webhook/payment',
    [WebhookController::class, 'payment']
);

Endpoint dapat diakses oleh provider:

https://example.com/api/webhook/payment

2. Simpan Secret di .env

Jangan menyimpan secret langsung di source code.

Gunakan:

WEBHOOK_SECRET=your-secret-key

Kemudian pada Laravel:

$secret = config('services.webhook.secret');

Tambahkan pada:

config/services.php
'webhook' => [
    'secret' => env('WEBHOOK_SECRET'),
],

Dengan cara ini secret tidak perlu ditulis langsung di controller.

3. Membuat Signature

Misalnya provider menggunakan HMAC SHA-256.

Data yang ditandatangani:

request body

Kemudian:

hash_hmac(
    'sha256',
    $payload,
    $secret
);

Contoh:

$signature = hash_hmac(
    'sha256',
    $payload,
    $secret
);

Hasilnya berupa string hash yang digunakan sebagai signature.

4. Mengambil Raw Request Body

Untuk signature verification, gunakan body asli request.

Di Laravel:

$payload = $request->getContent();

Jangan langsung menggunakan:

$request->all();

untuk proses signature jika provider menghitung signature berdasarkan raw body.

Perbedaan format JSON dapat menghasilkan signature yang berbeda.

5. Verifikasi Signature

Misalnya provider mengirim signature melalui header:

X-Webhook-Signature

Controller:

public function payment(Request $request)
{
    $payload = $request->getContent();

    $signature = $request->header(
        'X-Webhook-Signature'
    );

    $expected = hash_hmac(
        'sha256',
        $payload,
        config('services.webhook.secret')
    );

    if (!$signature ||
        !hash_equals($expected, $signature)
    ) {
        abort(401, 'Invalid signature');
    }

    // Proses webhook

    return response()->json([
        'message' => 'Webhook received',
    ]);
}

6. Mengapa Menggunakan hash_equals()?

Jangan membandingkan signature menggunakan:

if ($expected == $signature)

Gunakan:

hash_equals(
    $expected,
    $signature
);

hash_equals() dirancang untuk perbandingan string secara timing-safe sehingga lebih tepat digunakan ketika membandingkan signature autentikasi.

7. Tambahkan Timestamp

Signature saja belum selalu cukup.

Salah satu masalah yang perlu diperhatikan adalah replay attack.

Contohnya penyerang mendapatkan request webhook yang valid:

Payment: Rp500.000
Signature: valid

Kemudian request tersebut dikirim kembali berkali-kali.

Untuk mengurangi risiko ini, provider dapat menyertakan timestamp:

X-Webhook-Timestamp

Signature kemudian dihitung dari:

timestamp + "." + payload

Contoh:

$timestamp = $request->header(
    'X-Webhook-Timestamp'
);

$payload = $request->getContent();

$signedPayload =
    $timestamp . '.' . $payload;

$expected = hash_hmac(
    'sha256',
    $signedPayload,
    config('services.webhook.secret')
);

8. Periksa Timestamp

Jangan menerima timestamp yang terlalu lama.

Contohnya:

if (
    abs(now()->timestamp - (int) $timestamp)
    > 300
) {
    abort(
        401,
        'Webhook expired'
    );
}

Artinya request hanya diterima jika timestamp masih berada dalam rentang sekitar 5 menit.

Batas waktunya dapat disesuaikan dengan sistem provider.

9. Gunakan HTTPS

Webhook production harus menggunakan HTTPS:

https://example.com/api/webhook/payment

Hindari:

http://example.com/api/webhook/payment

HTTPS membantu melindungi data yang dikirim antara provider dan server aplikasi.

10. Jangan Menaruh Secret di URL

Hindari:

https://example.com/webhook?secret=123456

Secret pada URL dapat masuk ke log server, proxy, browser history, atau sistem monitoring.

Lebih baik menggunakan:

X-Webhook-Signature

atau header autentikasi yang disediakan provider.

11. Gunakan Idempotency

Webhook dapat dikirim lebih dari satu kali.

Contohnya:

Payment Success
↓
Webhook #1
Webhook #2
Webhook #3

Aplikasi harus dapat menangani request yang sama tanpa menggandakan transaksi.

Simpan ID event:

event_id

pada database.

Contohnya:

if (
    WebhookEvent::where(
        'event_id',
        $eventId
    )->exists()
) {
    return response()->json([
        'message' => 'Already processed',
    ]);
}

Dengan demikian event yang sama tidak diproses berulang kali.

12. Gunakan Database Transaction

Jika webhook mengubah beberapa data sekaligus, gunakan transaction.

Contohnya:

DB::transaction(function () use ($data) {

    $payment = Payment::create([
        'transaction_id' => $data['transaction_id'],
        'status' => 'paid',
    ]);

    Transaction::where(
        'id',
        $data['transaction_id']
    )->update([
        'status' => 'paid',
    ]);
});

Jika terjadi error, perubahan dapat dibatalkan secara otomatis.

13. Jangan Langsung Memproses Data Berat

Webhook sebaiknya memberikan response dengan cepat.

Jika proses membutuhkan waktu lama:

Webhook
↓
Validasi Signature
↓
Simpan Event
↓
Queue
↓
Response 200

Kemudian proses berat dilakukan oleh Laravel Queue.

Contohnya:

ProcessPaymentWebhook::dispatch(
    $data
);

Pendekatan ini membantu mencegah timeout dari provider.

14. Validasi Payload

Signature yang valid tidak berarti isi data otomatis benar.

Tetap lakukan validasi:

$data = $request->validate([
    'event_id' => ['required', 'string'],
    'transaction_id' => ['required', 'string'],
    'status' => [
        'required',
        'string',
    ],
]);

Kemudian proses data yang sudah divalidasi.

15. Jangan Percaya Status dari Request Saja

Misalnya webhook mengirim:

{
    "transaction_id": "TRX-001",
    "status": "paid"
}

Jangan langsung menganggap pembayaran valid hanya karena status tersebut diterima.

Untuk transaksi penting, sistem dapat melakukan verifikasi tambahan sesuai kemampuan provider.

Contohnya:

Webhook
↓
Signature Valid
↓
Event Valid
↓
Transaction Valid
↓
Update Status

16. Rate Limiting

Webhook juga dapat diberikan rate limiting sebagai lapisan perlindungan tambahan.

Namun, jangan menerapkan limit terlalu ketat karena provider dapat mengirim retry ketika webhook tidak mendapatkan response yang sesuai.

Gunakan batas yang sesuai dengan dokumentasi provider.

17. Logging Webhook

Simpan log yang cukup untuk troubleshooting:

Log::info(
    'Webhook payment received',
    [
        'event_id' => $eventId,
        'transaction_id' => $transactionId,
    ]
);

Hindari mencatat:

Password
Secret Key
Authorization Token
Data kartu
Data sensitif pelanggan

18. Contoh Controller yang Lebih Aman

Contoh sederhana:

public function payment(Request $request)
{
    $payload = $request->getContent();

    $signature = $request->header(
        'X-Webhook-Signature'
    );

    $timestamp = $request->header(
        'X-Webhook-Timestamp'
    );

    if (!$signature || !$timestamp) {
        return response()->json([
            'message' => 'Invalid webhook',
        ], 401);
    }

    if (
        abs(now()->timestamp - (int) $timestamp)
        > 300
    ) {
        return response()->json([
            'message' => 'Webhook expired',
        ], 401);
    }

    $signedPayload =
        $timestamp . '.' . $payload;

    $expected = hash_hmac(
        'sha256',
        $signedPayload,
        config('services.webhook.secret')
    );

    if (!hash_equals(
        $expected,
        $signature
    )) {
        return response()->json([
            'message' => 'Invalid signature',
        ], 401);
    }

    $data = json_decode(
        $payload,
        true
    );

    if (!$data) {
        return response()->json([
            'message' => 'Invalid payload',
        ], 400);
    }

    ProcessPaymentWebhook::dispatch($data);

    return response()->json([
        'message' => 'Webhook received',
    ]);
}

Implementasi sebenarnya harus mengikuti format signature dari provider yang digunakan.

Contoh Alur Webhook yang Aman

Provider
   ↓
HTTPS
   ↓
Laravel Webhook
   ↓
Validasi Timestamp
   ↓
Verifikasi Signature
   ↓
Validasi Payload
   ↓
Cek Event ID
   ↓
Simpan Event
   ↓
Queue
   ↓
Update Transaksi

Dengan alur tersebut, webhook memiliki beberapa lapisan perlindungan.

Kesalahan yang Perlu Dihindari

Hanya Memeriksa IP

IP address dapat berubah dan tidak selalu menjadi mekanisme autentikasi yang cukup.

Tidak Memverifikasi Signature

Siapa pun dapat mencoba memanggil endpoint webhook.

Menggunakan Request Data yang Sudah Diparse

Signature sering dihitung berdasarkan raw body. Gunakan:

$request->getContent();

sesuai aturan provider.

Tidak Menggunakan hash_equals()

Gunakan perbandingan timing-safe untuk signature.

Tidak Menangani Duplicate Event

Webhook dapat dikirim ulang sehingga idempotency penting.

Memproses Data Terlalu Lama

Gunakan queue untuk proses berat.

Menyimpan Secret di Source Code

Gunakan .env dan konfigurasi Laravel.

Webhook Laravel Bersama Ovla Media

Ovla Media dapat membantu membangun integrasi webhook yang aman untuk:

  1. Payment gateway.

  2. WhatsApp API.

  3. Marketplace.

  4. Sistem ERP.

  5. CRM.

  6. Sistem POS.

  7. Sistem klinik.

  8. Payment notification.

  9. Sinkronisasi data.

  10. Aplikasi bisnis custom.

Webhook juga dapat dilengkapi dengan signature verification, idempotency, queue, logging, retry handling, dan monitoring.

Kesimpulan

Webhook merupakan bagian penting dari integrasi antar sistem, tetapi endpoint webhook harus dilindungi agar tidak mudah dipalsukan.

Lapisan keamanan yang dapat digunakan:

HTTPS
↓
Signature Verification
↓
Timestamp
↓
Payload Validation
↓
Idempotency
↓
Queue
↓
Logging

Untuk signature berbasis HMAC, Laravel dapat menggunakan:

$expected = hash_hmac(
    'sha256',
    $payload,
    $secret
);

hash_equals(
    $expected,
    $signature
);

Namun, format signature harus mengikuti dokumentasi layanan yang mengirim webhook.

Dengan implementasi yang tepat, Laravel dapat menerima webhook dari berbagai layanan eksternal dengan lebih aman, terkontrol, dan mudah dimonitor.

#Webhook Laravel #Laravel Webhook #Webhook Security #Signature Verification #HMAC Laravel #Laravel API #Laravel Security #Payment Gateway Laravel #Webhook Payment #Laravel Queue #Idempotency Laravel #Laravel Integration #REST API Laravel #Laravel Production #API Security #PHP Laravel #Integrasi Sistem #Web Development #Ovla Media #Jasa Laravel
Beranda Produk Artikel
Konsultasi
OVLA

Navigasi Utama

Hubungi Kami

Mulai Konsultasi Sekarang