Skip to content
On this page

Use incoming webhooks to get real-time updates

Listen for events on your Payske account so your integration can automatically trigger reactions.

Payske uses webhooks to notify your application when an event happens in your account. Webhooks are particularly useful for asynchronous events like when a customer’s bank confirms a payment, a customer disputes a charge, a recurring payment succeeds, or when collecting subscription payments.

Ready to go live? Register your webhook endpoint on the Dashboard so Payske knows where to deliver events.

How Payske uses webhooks

A webhook enables Payske to push real-time notifications to your app. Payske uses HTTPS to send these notifications to your app as a JSON payload. You can then use these notifications to execute actions in your backend systems. To learn more, see Payske webhook events overview.

Steps to receive webhooks

You can start receiving event notifications in your app using the steps in this section:

  1. Identify the events you want to monitor and the event payloads to parse.
  2. Create a webhook endpoint as an HTTP endpoint (URL) on your local server.
  3. Handle requests from Payske by parsing each event object and returning 2xx response status codes.
  4. Test that your webhook endpoint is working properly using the Payske CLI.
  5. Deploy your webhook endpoint so it’s a publicly accessible HTTPS URL.
  6. Register your publicly accessible HTTPS URL in the Payske dashboard.

How to create a webhook endpoint

Creating a webhook endpoint is no different from creating any other page on your website. It’s an HTTP or HTTPS endpoint on your server with a URL. If you’re still developing your endpoint on your local machine, it can be HTTP. After it’s publicly accessible, it must be HTTPS. You can use one endpoint to handle several different event types at once, or set up individual endpoints for specific events.

Step 1: Identify the events to monitor

Use the API reference guide to identify the Payske events and their event objects your webhook endpoint needs to parse.

Step 2: Create a webhook endpoint

Set up an HTTP endpoint on your local machine that can accept unauthenticated webhook requests with a POST method. For example, this route in Flask is a map to a Python webhook function:

python
@app.route('/payske_webhooks', methods=['POST'])
def webhook():
    payske_payload = request.json

In this example, the /payske_webhooks route is configured to accept only POST requests and expects data to be delivered in a JSON payload.

Step 3: Handle requests from Payske

Your endpoint must be configured to read event objects for the type of event notifications you want to receive. Payske sends events to your webhook endpoint as part of a POST request with a JSON payload.

Check event objects

Each event is structured as an event object with a type, id, and related Payske resource nested under data. Your endpoint must check the event type and parse the payload of each event.

json
{
  "id": "evt_2Zj5zzFU3a9abcZ1aYYYaaZ1",
  "object": "event",
  "api_version": "2022-11-15",
  "created": 1633887337,
  "data": {
    "object": {...}
}

Return a 2xx response

Your endpoint must quickly return a successful status code (2xx) prior to any complex logic that could cause a timeout. For example, you must return a 200 response before updating a customer’s invoice as paid in your accounting system.

python

@app.route('/payske_webhooks', methods=['POST'])
def webhook():
    event = None
    payload = request.data
    sig_header = request.headers['PAYSKE_SIGNATURE']

    try:
        event = payske.Webhook.construct_event(
            payload, sig_header, endpoint_secret
        )
    except ValueError as e:
        # Invalid payload
        raise e
    except payske.error.SignatureVerificationError as e:
        # Invalid signature
        raise e

    # Handle the event
    print('Handled event type {}'.format(event['type']))

    return jsonify(success=True)

In the above example, the Python function checks that the event type was received, and returns a 200 response.

Built-in retries

Payske webhooks have built-in retry methods for 3xx, 4xx, or 5xx response status codes. If Payske doesn’t quickly receive a 2xx response status code for an event, we mark the event as failed and stop trying to send it to your endpoint. After multiple days, we email you about the misconfigured endpoint, and automatically disable it soon after if you haven’t addressed it.

Sample code

ruby
require 'json'

# Using Sinatra
post '/webhook' do
  payload = request.body.read
  event = nil

  begin
    event = Payske::Event.construct_from(
      JSON.parse(payload, symbolize_names: true)
    )
  rescue JSON::ParserError => e
    # Invalid payload
    status 400
    return
  end

  # Handle the event
  case event.type
  when 'payment_intent.succeeded'
    payment_intent = event.data.object # contains a Payske::PaymentIntent
    # Then define and call a method to handle the successful payment intent.
    # handle_payment_intent_succeeded(payment_intent)
  when 'payment_method.attached'
    payment_method = event.data.object # contains a Payske::PaymentMethod
    # Then define and call a method to handle the successful attachment of a PaymentMethod.
    # handle_payment_method_attached(payment_method)
  # ... handle other event types
  else
    puts "Unhandled event type: #{event.type}"
  end

  status 200
end
python
import json
from django.http import HttpResponse

# Using Django
@csrf_exempt
def my_webhook_view(request):
  payload = request.body
  event = None

  try:
    event = payske.Event.construct_from(
      json.loads(payload), payske.api_key
    )
  except ValueError as e:
    # Invalid payload
    return HttpResponse(status=400)

  # Handle the event
  if event.type == 'payment_intent.succeeded':
    payment_intent = event.data.object # contains a payske.PaymentIntent
    # Then define and call a method to handle the successful payment intent.
    # handle_payment_intent_succeeded(payment_intent)
  elif event.type == 'payment_method.attached':
    payment_method = event.data.object # contains a payske.PaymentMethod
    # Then define and call a method to handle the successful attachment of a PaymentMethod.
    # handle_payment_method_attached(payment_method)
  # ... handle other event types
  else:
    print('Unhandled event type {}'.format(event.type))

  return HttpResponse(status=200)
php
$payload = @file_get_contents('php://input');
$event = null;

try {
    $event = \Payske\Event::constructFrom(
        json_decode($payload, true)
    );
} catch(\UnexpectedValueException $e) {
    // Invalid payload
    http_response_code(400);
    exit();
}

// Handle the event
switch ($event->type) {
    case 'payment_intent.succeeded':
        $paymentIntent = $event->data->object; // contains a \Payske\PaymentIntent
        // Then define and call a method to handle the successful payment intent.
        // handlePaymentIntentSucceeded($paymentIntent);
        break;
    case 'payment_method.attached':
        $paymentMethod = $event->data->object; // contains a \Payske\PaymentMethod
        // Then define and call a method to handle the successful attachment of a PaymentMethod.
        // handlePaymentMethodAttached($paymentMethod);
        break;
    // ... handle other event types
    default:
        echo 'Received unknown event type ' . $event->type;
}

http_response_code(200);
java
// Using the Spark framework (http://sparkjava.com)
public Object handle(Request request, Response response) {
  String payload = request.body();
  Event event = null;

  try {
    event = ApiResource.GSON.fromJson(payload, Event.class);
  } catch (JsonSyntaxException e) {
    // Invalid payload
    response.status(400);
    return "";
  }

  // Deserialize the nested object inside the event
  EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
  PayskeObject payskeObject = null;
  if (dataObjectDeserializer.getObject().isPresent()) {
    payskeObject = dataObjectDeserializer.getObject().get();
  } else {
    // Deserialization failed, probably due to an API version mismatch.
    // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for
    // instructions on how to handle this case, or return an error here.
  }

  // Handle the event
  switch (event.getType()) {
    case "payment_intent.succeeded":
      PaymentIntent paymentIntent = (PaymentIntent) payskeObject;
      // Then define and call a method to handle the successful payment intent.
      // handlePaymentIntentSucceeded(paymentIntent);
      break;
    case "payment_method.attached":
      PaymentMethod paymentMethod = (PaymentMethod) payskeObject;
      // Then define and call a method to handle the successful attachment of a PaymentMethod.
      // handlePaymentMethodAttached(paymentMethod);
      break;
    // ... handle other event types
    default:
      System.out.println("Unhandled event type: " + event.getType());
  }

  response.status(200);
  return "";
}
typescript
// This example uses Express to receive webhooks
const express = require('express');
const app = express();

// Match the raw body to content type application/json
// If you are using Express v4 - v4.16 you need to use body-parser, not express, to retrieve the request body
app.post('/webhook', express.json({type: 'application/json'}), (request, response) => {
  const event = request.body;

  // Handle the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object;
      // Then define and call a method to handle the successful payment intent.
      // handlePaymentIntentSucceeded(paymentIntent);
      break;
    case 'payment_method.attached':
      const paymentMethod = event.data.object;
      // Then define and call a method to handle the successful attachment of a PaymentMethod.
      // handlePaymentMethodAttached(paymentMethod);
      break;
    // ... handle other event types
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  // Return a response to acknowledge receipt of the event
  response.json({received: true});
});

app.listen(8000, () => console.log('Running on port 8000'));
go
http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) {
    const MaxBodyBytes = int64(65536)
    req.Body = http.MaxBytesReader(w, req.Body, MaxBodyBytes)
    payload, err := ioutil.ReadAll(req.Body)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err)
        w.WriteHeader(http.StatusServiceUnavailable)
        return
    }

    event := payske.Event{}

    if err := json.Unmarshal(payload, &event); err != nil {
        fmt.Fprintf(os.Stderr, "Failed to parse webhook body json: %v\n", err.Error())
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    // Unmarshal the event data into an appropriate struct depending on its Type
    switch event.Type {
    case "payment_intent.succeeded":
        var paymentIntent payske.PaymentIntent
        err := json.Unmarshal(event.Data.Raw, &paymentIntent)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err)
            w.WriteHeader(http.StatusBadRequest)
            return
        }
        // Then define and call a func to handle the successful payment intent.
        // handlePaymentIntentSucceeded(paymentIntent)
    case "payment_method.attached":
        var paymentMethod payske.PaymentMethod
        err := json.Unmarshal(event.Data.Raw, &paymentMethod)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err)
            w.WriteHeader(http.StatusBadRequest)
            return
        }
        // Then define and call a func to handle the successful attachment of a PaymentMethod.
        // handlePaymentMethodAttached(paymentMethod)
    // ... handle other event types
    default:
        fmt.Fprintf(os.Stderr, "Unhandled event type: %s\n", event.Type)
    }

    w.WriteHeader(http.StatusOK)
})
csharp
using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Payske;

namespace workspace.Controllers
{
    [Route("api/[controller]")]
    public class PayskeWebHook : Controller
    {
        [HttpPost]
        public async Task<IActionResult> Index()
        {
            var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();

            try
            {
                var payskeEvent = EventUtility.ParseEvent(json);

                // Handle the event
                if (payskeEvent.Type == Events.PaymentIntentSucceeded)
                {
                    var paymentIntent = payskeEvent.Data.Object as PaymentIntent;
                    // Then define and call a method to handle the successful payment intent.
                    // handlePaymentIntentSucceeded(paymentIntent);
                }
                else if (payskeEvent.Type == Events.PaymentMethodAttached)
                {
                    var paymentMethod = payskeEvent.Data.Object as PaymentMethod;
                    // Then define and call a method to handle the successful attachment of a PaymentMethod.
                    // handlePaymentMethodAttached(paymentMethod);
                }
                // ... handle other event types
                else
                {
                    // Unexpected event type
                    Console.WriteLine("Unhandled event type: {0}", payskeEvent.Type);
                }
                return Ok();
            }
            catch (PayskeException e)
            {
                return BadRequest();
            }
        }
    }
}