Skip to content

Webhooks

Webhooks are used to send real-time events to an HTTP endpoint, for instance when a payment is paid. You can create webhooks and setup an endpoint to receive these events.

Examples of events:

  • Payment has been paid, payment.paid
  • Chargeback created, chargeback.created
  • Subscription details updated, subscription.updated

Getting started

To receive events from webhooks you have to do the following:

  1. Create a webhook endpoint handler to receive event POST requests.
  2. Create a Webhook with an endpoint pointing to your webhook endpoint handler.

NOTE

You can create Webhooks in our Dashboard or through the API.

Webhook endpoint handler

Set up an HTTPS endpoint that can accept POST requests from PayPro. This endpoint must use HTTPS to ensure the connection is secure.

The handler you build must do three things:

  1. Read the raw POST body and parse the JSON consisting of an Event object.
  2. Verify that the webhook is correct and not tampered with.
  3. Return a success status code (200). You should do this in a timely manner before the request can timeout.

Example endpoint

This code snippet of a webhook endpoint handler reads the POST body, parses the JSON and handles Event object. If successful it will return a 200 response.

php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;

use PayPro\Webhook;
use PayPro\Exception\ApiErrorException;

// We are using the Laravel framework
class WebhookController extends Controller
{
  public function index(Request $request)
  {
    $signature = $request->header('PayPro-Signature');
    $timestamp = $request->header('PayPro-Timestamp');

    // The secret of the Webhook which is used to generate the signature.
    // You can find this in the PayPro dashboard and every webhook has its own secret.
    $secret = '...';

    $event = null;

    // Try to create an Event object based on the request. Here we also verify that
    // the signature send is valid.
    try {
      $event = new PayPro\Webhook::createEvent(
        $request->json()->all(),
        $signature,
        $secret,
        $timestamp
      );
    } catch(PayPro\Exception\ApiErrorException $e) {
      return response(null, 400);
    }

    // Check event type and call the correct method.
    switch ($event->event_type) {
      case 'payment.paid':
        // Handle payment.paid event
        $this->handlePaymentPaid($event->payload);
        break;
      case 'refund.refunded':
        // Handle refund.refunded event
        $this->handleRefundRefunded($event->payload);
        break;
    }

    return response(null, 200);
  }
}
ruby
# We are using the Rails framework
class WebhookController < ApplicationController
  # Make sure we don't do CSRF protection for the webhook endpoint
  protect_from_forgery except: :index

  def index
    signature = request.headers['HTTP_PAYPRO_SIGNATURE']
    timestamp = request.headers['HTTP_PAYPRO_TIMESTAMP']

    # The secret of the Webhook which is used to generate the signature.
    # You can find this in the PayPro dashboard and every webhook has its own secret.
    secret = '...'

    event = nil

    # Try to create an Event object based on the request. Here we also verify that
    # the signature send is valid.
    begin
      json = JSON.parse(request.raw_post)

      event = PayPro::Webhook.create_event(
        json,
        signature,
        secret,
        timestamp
      )
    rescue PayPro::Error
      head :bad_request
    end

    # Check event type and call the correct method.
    case event.event_type
    when 'payment.paid'
      # Handle payment.paid event
      handle_payment_paid(event.payload)
    when 'refund.refunded'
      # Handle refund.refunded event
      handle_refund_refunded(event.payload)
    end

    head :ok
  end
end

Verify signature manually

Although we recommend that you use our official libraries to verify webhook signatures, it is possible to do this manually when necessary.

The webhook request will include two headers that are used to verify if webhook is valid.

  1. PayPro-Signature, containing the signature of webhook request
  2. PayPro-Timestamp, containing the timestamp of when the webhook was sent

The signature is generated using HMAC using the SHA-256 hasing algorithm.

Another important variable is the secret of the webhook. This secret is used as the key in the HMAC computation.

To manually verify the webhook signature you have to follow the following steps:

Step 1: Get the signature and timestamp headers from the request

The headers PayPro-Signature and PayPro-Timestamp should be extracted from the webhook request.

Step 2: Create the payload string

To create the payload for the HMAC computation we need to contact the following strings:

  • The timestamp from the PayPro-Timestamp header
  • The character .
  • The JSON body of the request (unaltered)

Step 3: Generate the signature to compare with

Compute an HMAC with the SHA-256 hash function. You must use the webhook secret as the key, and use the payload string as the data. You can find your webhook secrets in the Dashboard

Step 4: Compare the signatures

You can now compact the signature you generated to the signature in the PayPro-Signature header. If these are equal the webhook is valid.

The PayPro-Timestamp can be compared to the current timestamp to determine if the webhook was received in the tolerable time. Our libraries use 10 minutes.

WARNING

To protect yourself from timing attacks you should make sure to use a constant-time string comparison function.

Webhook behavior

This section explains certain webhook behaviors which are important to take into mind when developing a webhook endpoint handler.

Retry behavior

PayPro will try multiple times for over 3 days with exponential back off to deliver the webhooks. PayPro only considers a successful webhook request when we get a HTTP status code in the 2xx range back from your webhook endpoint handler.

Event ordering

PayPro doesn't guarantee the order of delivery events are in the order the events were generated. Your webhook endpoint handler shouldn't expect the order of events and handle cases where the events were missed to fetch events or resources from our API.

Duplicates

In some cases it can happen that an event is received twice by your endpoint handler. You should be aware of this and handle these cases. All events have a unique ID which you could use to deduplicate them.