Trigger Event upon receiving Mailgun Webhook

Created on 8 October 2020, about 4 years ago
Updated 5 November 2023, about 1 year ago

Problem/Motivation

It would be useful to trigger a Drupal Event when receiving a Mailgun Webhook. It could be used to track hard or soft bounces. Developers will create their own EventSubscribers to handle the webhook.

Proposed resolution

Create an endpoint for Mailgun webhooks, verify the webhook, and fire a Drupal Event containing two properties:
type and payload.
Here is a sample webhook:

{
  “signature”:
  {
    "timestamp": "1529006854",
    "token": "a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0",
    "signature": "d2271d12299f6592d9d44cd9d250f0704e4674c30d79d07c47a66f95ce71cf55"
  }
  “event-data”:
  {
    "event": "opened",
    "timestamp": 1529006854.329574,
    "id": "DACSsAdVSeGpLid7TN03WA",
    // ...
  }
}

type would be the value of the event property, and the payload would be the entire payload.

Remaining tasks

Create a Controller for the webhook endpoint in Drupal, in which the webhook is verified.
See https://documentation.mailgun.com/en/latest/user_manual.html#webhooks for webhook documentation. For verifying the webhook, from the "Making it secure" section of https://www.mailgun.com/blog/a-guide-to-using-mailguns-webhooks/ :

There’s nothing to stop someone who knows our webhook URL from crafting false event data and sending it to the URL. Luckily, Mailgun signs each request sent and posts the following parameters as well:

  • timestamp (number of seconds passed since January 1, 1970)
  • token (randomly generated string with length 50)
  • signature (hexadecimal string generated by HMAC algorithm)

To verify the token, you need to:

  • Concatenate the values of timestamp and token.
  • Encode the resulting string with HMAC, using your Mailgun API key as the key and Sha256 as the algorithm.

The result should be the same as the signature.

User interface changes

None. Firing the event would be automatic. Webhooks won't be received unless a user configures their Mailgun account to send them anyway.

API changes

Addition of the Mailgun Webhook event.

Data model changes

None.

Feature request
Status

Active

Version

1.0

Component

Code

Created by

🇨🇦Canada shaundychko

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • First commit to issue fork.
  • Assigned to Panchuk
  • 🇺🇦Ukraine Panchuk Volyn, Lutsk
  • 🇺🇦Ukraine Panchuk Volyn, Lutsk
  • Open in Jenkins → Open on Drupal.org →
    Core: 9.5.x + Environment: PHP 7.4 & MySQL 5.7
    last update about 1 year ago
    5 pass
  • Issue was unassigned.
  • Status changed to Needs review about 1 year ago
  • 🇺🇦Ukraine Panchuk Volyn, Lutsk

    I've created a submodule to trigger the event. I think there are a few things that must be fixed before merging.

    1) Autotests
    It will be nice to cover some bases with tests.

    2) Routing URL
    I'm not sure if it is okay to keep static URL in route.

    3) Sample subscriber for debugging
    I've committed the subscriber, probably it useless, and better to remove it.

  • 🇬🇧United Kingdom altcom_neil

    @Panchuk
    Good work on this.

    For number 2 raised above you can use a dynamic path. So change the routing to:

    # Endpoint for the webhooks.
    mailgun_webhooks.endpoint:
      path: '/api/mailgun/{webhook_key}'
      defaults:
        _title: 'Mailgun Webhook'
        _controller: '\Drupal\mailgun_webhooks\Controller\MailgunWebhookController::processWebhook'
      methods: [POST]
      requirements:
        # Allow the endpoint to be accessed without authentication.
        # This is required for Mailgun to be able to access the endpoint.
        _access: 'TRUE'
        _format: 'json'
    

    And then the \Drupal\mailgun_webhooks\Controller\MailgunWebhookController::processWebhook is changed to expect a $webhook_key variable and compare it with a config var so it can be set to a unique value in every site.

      public function processWebhook($webhook_key): Response {
        // If the webhook_key value provided in the URL does not match the one in
        // the current config throw a 401: Unauthorized error.
        if ($webhook_key !== $this->config->get('webhook_key')) {
          $response = new Response('Unauthorized: Webhook key does not match.');
          $response->setStatusCode('401');
          return $response;
        }
    
    

    I added a hook_install to generate the URL when the module is installed:

    function mailgun_webhooks_install() {
      // On install generate a random string to use in the callback URL.
      $random = new Random();
      $webhook_key = $random->name(48, TRUE);
      $config = \Drupal::configFactory()->getEditable('mailgun_webhooks.settings');
      $config->set('webhook_key', $webhook_key);
      $config->save();
    }
    

    If someone tries to use the wrong webhook_key you get the 401 error from MailgunWebhookController and if you are missing the webhook_key entirely (using path /api/mailgun) then you just get the standard Drupal 404 page.

    Cheers, Neil

  • 🇬🇧United Kingdom altcom_neil

    In \Drupal\mailgun_webhooks\Controller\MailgunWebhookController the old RequestStack use needs to be replaced with the symfony version.

    - use Drupal\Core\Http\RequestStack;
    use Drupal\Component\Serialization\Json;
    use Drupal\Core\Controller\ControllerBase;
    use Symfony\Component\HttpFoundation\Response;
    use Drupal\Core\Config\ConfigFactoryInterface;
    use Drupal\mailgun_webhooks\Event\MailgunWebhookEvent;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    use Symfony\Component\EventDispatcher\EventDispatcherInterface;
    + use Symfony\Component\HttpFoundation\RequestStack;
    use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
    
  • 🇺🇦Ukraine Panchuk Volyn, Lutsk

    Thanks for review @Altcom_Neil

    Do you want to commit these improvements/fixes? If not I'll prepare the update for MR.

  • 🇺🇦Ukraine Panchuk Volyn, Lutsk

    Added code suggested by @altcom_neil and fixed a few issues.

    Next time going to add some tests, but for now it's enough.

Production build 0.71.5 2024