spatie/laravel-webhook-server

repository·main·Indexed 22 days ago

https://github.com/spatie/laravel-webhook-server

A Laravel package for sending webhooks from an application to other services. It features built-in request signing using HMAC SHA256, automated retries with configurable backoff strategies, and deep integration with Laravel queues. The package provides a fluent API via WebhookCall to manage payloads, custom HTTP verbs, mTLS authentication, and raw body transmissions, while offering a suite of events to monitor success and failure.

Tokens
5.6K
Snippets
21
Records
27
Agent score
74%

What's inside spatie/laravel-webhook-server

  1. How signing requests works

    main

    The package signs the payload to prevent tampering. It calculates an HMAC SHA256 signature using the provided secret and the JSON-encoded payload. The resulting signature is sent in the Signature header.

    Calculation logic:

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

    To skip signing, call doNotSign().

    WebhookCall::create()
       ->doNotSign()
        ...
        ->dispatch();
  2. Install the laravel-webhook-server package

    main

    Install the package via Composer and publish the configuration file to your Laravel application.

    composer require spatie/laravel-webhook-server
    
    php artisan vendor:publish --provider="Spatie\WebhookServer\WebhookServerServiceProvider"
  3. Configure queues for webhook delivery

    main
    The package uses Laravel queues to handle webhook delivery and retries. In non-local environments, ensure you have configured a real queue driver (e.g., redis, database) instead of using the sync driver to ensure webhooks are processed asynchronously.
  4. Configure the webhook server

    main

    After publishing, you can customize the webhook behavior in config/webhook-server.php. Key configuration options include:

    • queue: The default Laravel queue used for sending requests.
    • http_verb: The HTTP method used (e.g., post).
    • signer: The class responsible for calculating request signatures.
    • signature_header_name: The header name for the signature.
    • timestamp_header_name: The header name for the timestamp.
    • headers: An array of headers to include in every request.
    • timeout_in_seconds: How long to wait before considering an attempt failed.
    • tries: Number of retry attempts.
    • backoff_strategy: The strategy used to determine delay between retries.
    • webhook_job: The job class used to dispatch webhooks.
    • verify_ssl: Whether to verify the destination's SSL certificate.
    • throw_exception_on_failure: Whether to throw an exception when all retries fail.
    • tags: Tags for Laravel Horizon.
    return [
    
        /*
         *  The default queue that should be used to send webhook requests.
         */
        'queue' => 'default',
    
        /*
         * The default http verb to use.
         */
        'http_verb' => 'post',
    
        /*
         * This class is responsible for calculating the signature that will be added to
         * the headers of the webhook request. A webhook client can use the signature
         * to verify the request hasn't been tampered with.
         */
        'signer' => \Spatie\WebhookServer\Signer\DefaultSigner::class,
    
        /*
         * This is the name of the header where the signature will be added.
         */
        'signature_header_name' => 'Signature',
        
        /*
         * This is the name of the header where the timestamp will be added.
         */
        'timestamp_header_name' => 'Timestamp',
    
        /*
         * These are the headers that will be added to all webhook requests.
         */
        'headers' => [],
    
        /*
         * If a call to a webhook takes longer this amount of seconds
         * the attempt will be considered failed.
         */
        'timeout_in_seconds' => 3,
    
        /*
         * The amount of times the webhook should be called before we give up.
         */
        'tries' => 3,
    
        /*
         * This class determines how many seconds there should be between attempts.
         */
        'backoff_strategy' => \Spatie\WebhookServer\BackoffStrategy\ExponentialBackoffStrategy::class,
    
        /*
         * This class is used to dispatch webhooks onto the queue.
         */
        'webhook_job' => \Spatie\WebhookServer\CallWebhookJob::class,
    
        /*
         * By default we will verify that the ssl certificate of the destination
         * of the webhook is valid.
         */
        'verify_ssl' => true,
        
        /*
         * When set to true, an exception will be thrown when the last attempt fails
         */
        'throw_exception_on_failure' => false,
    
        /*
         * When using Laravel Horizon you can specify tags that should be used on the
         * underlying job that performs the webhook request.
         */
        'tags' => [],
    ];
  5. How the DefaultSigner calculates signatures

    main

    The DefaultSigner implements the Signer interface to generate HMAC signatures for webhook payloads. It encodes the $payload array into a JSON string and then calculates a sha256 HMAC hash using the provided $secret.

    Signature calculation logic:

    1. json_encode($payload)
    2. hash_hmac('sha256', $payloadJson, $secret)
    <?php
    
    // The signature is generated using sha256 HMAC on the JSON-encoded payload
    $signature = hash_hmac('sha256', json_encode($payload), $secret);
  6. Test webhook dispatching using Bus fake

    main

    When writing automated tests, use Bus::fake() to prevent actual webhooks from being sent to external websites. You can then assert that the Spatie\WebhookServer\CallWebhookJob was dispatched.

    use Illuminate\\|Support\\Facades\\Bus;
    use Spatie\\WebhookServer\\CallWebhookJob;
    use Tests\\TestCase;
    
    class TestFile extends TestCase
    {
        public function testJobIsDispatched()
        {
            Bus::fake();
    
            // ... Perform webhook call ...
    
            Bus::assertDispatched(CallWebhookJob::class);
        }
    }
  7. Test webhook queuing using Queue fake

    main

    To ensure webhooks are correctly queued without executing them during tests, use Queue::fake(). You can then assert that the Spatie\WebhookServer\CallWebhookJob was pushed to the queue.

    use Illuminate\\Support\\Facades\\Queue;
    use Spatie\\WebhookServer\\CallWebhookJob;
    use Tests\\TestCase;
    
    class TestFile extends TestCase
    {
        public function testJobIsQueued()
        {
            Queue::fake();
    
            // ... Perform webhook call ...
    
            Queue::assertPushed(CallWebhookJob::class);
        }
    }
  8. Basic usage of WebhookCall

    main

    To send a webhook, use the WebhookCall::create() fluent API. By default, it sends a POST request with a JSON-encoded payload. If you provide a secret via useSecret(), the package adds a Signature header to allow the receiver to verify the payload integrity.

    WebhookCall::create()
       ->url('https://other-app.com/webhooks')
       ->payload(['key' => 'value'])
       ->useSecret('sign-using-this-secret')
       ->dispatch();
  9. Use Mutual TLS (mTLS) authentication

    main

    To authenticate both the client and server, use the mutualTls() method. This requires providing certificate and key paths.

    WebhookCall::create()
        ->mutualTls(
            certPath: storage_path('path/to/cert.pem'), 
            certPassphrase: 'optional_cert_passphrase', 
            sslKeyPath: storage_path('path/to/key.pem'), 
            sslKeyPassphrase: 'optional_key_passphrase'
        )
  10. Customize HTTP verb, headers, and proxy

    main

    You can override default request settings for specific calls:

    • HTTP Verb: Use useHttpVerb('get') (defaults to post).
    • Extra Headers: Use withHeaders(['Key' => 'Value']).
    • Proxy: Use useProxy('http://proxy.server:3128') (follows Guzzle proxy format).
    • SSL Verification: Use doNotVerifySsl() to disable SSL certificate verification.
    WebhookCall::create()
        ->useHttpVerb('get')
        ->withHeaders(['Another Header' => 'Value'])
        ->useProxy('http://proxy.server:3128')
        ->doNotVerifySsl()
        ...
        ->dispatch();
  11. Send raw string bodies

    main

    By default, payloads are JSON encoded. To send a raw string (e.g., XML), use sendRawBody(string $body).

    Warning: Due to a type mismatch in the Signer API, you cannot sign raw data requests. You must call doNotSign() when using raw bodies.

    WebhookCall::create()
        ->sendRawBody("<root>someXMLContent</root>")
        ->doNotSign()
        ...
        ->dispatch();