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

  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 Smartpay by parsing each event object and returning 200 response status codes.
  4. Register your publicly accessible HTTPS URL with Smartpay using the create a webhook endpoint API endpoint .
  5. Secure your webhooks (recommended).
  6. Deploy your webhook endpoint so it is a publicly accessible HTTPS URL.
  7. Test that your webhook endpoint is working properly.

1. Identify the events you want to monitor

Take a look at the supported event types to decide which events you are interested in subscribing to. Check the event object and sample events to see the structure of the objects you need to parse.

2. Create a webhook endpoint

Creating a webhook endpoint is no different from creating any other page on your website. It’s an HTTPS endpoint on your server with a URL. If you’re still developing your endpoint on your local machine, it can be HTTP. On production, 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.

3. Handle requests from Smartpay

Your endpoint must be configured to read event objects for the type of event notifications you want to receive. Smartpay 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 an id, type and related Smartpay resource nested under data(the structure of data depends on the event type, as it is the object the event relates to). For more details see the event object. Your endpoint must check the event type and parse the payload of each event.

  "id": "evt_test_yETRfprzCxFsSJaJZPIENt",
  "object": "event",
  "createdAt": 1664522079584,
  "test": true,
  "eventData": {
    "type": "order.authorized",
    "version": "2022-02-18",
    "data": {...}

Return a 200 response

Your endpoint should quickly return a successful status code (200) prior to any complex logic that could cause a timeout.

The examples below verify the webhook signature (see step 5) and return a 200 HTTP status code. You can find the example code on GitHub as well.

package main

import (

func webhook(w http.ResponseWriter, r *http.Request) {
    signature := r.Header.Get("Smartpay-Signature")
    calculatedSignature := r.Header.Get("Calculated-Signature")

    if signature != calculatedSignature {
        log.Println("Signature verification failed.", signature, calculatedSignature)

    var bodyBytes []byte
    var err error

    if r.Body != nil {
        bodyBytes, err = ioutil.ReadAll(r.Body)
        if err != nil {
            log.Printf("Body reading error: %v", err)
        defer r.Body.Close()

    log.Printf("Headers: %+v\n", r.Header)

    if len(bodyBytes) > 0 {
        var prettyJSON bytes.Buffer
        if err = json.Indent(&prettyJSON, bodyBytes, "", "\t"); err != nil {
            log.Printf("JSON parse error: %v", err)
    } else {
        log.Printf("Body: No Body Supplied\n")


func main() {
    mux := http.NewServeMux()

    webhookHandler := http.HandlerFunc(webhook)
    mux.Handle("/", smartpay.CalculateWebhookSignatureMiddleware("YOUR_SIGNING_KEY", webhookHandler))

    log.Print("Listening on :3000...")
    err := http.ListenAndServe("", mux)
const crypto = require('crypto');
const express = require('express');
const createError = require('http-errors');

const Smartpay = require('@smartpay/sdk-node').default; // The Nodejs SDK

const app = express();

const secret = 'YOUR_SIGNING_KEY';

const log = new Map();

    verify: Smartpay.expressWebhookMiddleware(secret),
);'/webhooks', async (req, res, next) => {
  const event = req.body;
  const signature = req.headers['smartpay-signature'];
  const calculatedSignature = req.headers['calculated-signature'];


  if (signature === calculatedSignature) {
    const key = req.headers['smartpay-event-id'];
    const existing = log.get(key) || {
      count: 0,

    console.log(`key: ${key} count: ${existing.count}`);

    if (existing && existing.count >= 2) {



    log.set(key, { count: existing.count + 1 });


app.listen(3000, '', () =>
  console.log('Node server listening on port 3000!')

require __DIR__ . '/./vendor/autoload.php';

use Tuupola\Base62;

$base62 = new Base62(["characters" => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789']);
$secret = 'YOUR_SIGNING_KEY';

$app = new \Slim\App;

$app->map(['get', 'post'], '/', function ($request, $response, $args) use (&$base62, &$secret) {
    $fileName = trim($request->getUri()->getBasePath(), '/');

    if ($fileName == 'webhooks') {

        $headers = $request->getHeaders();
        $smartpaySignature = $request->getHeader('Smartpay-Signature')[0];
        $smartpaySignatureTimestamp = $request->getHeader('Smartpay-Signature-Timestamp')[0];

        if ($smartpaySignature && $smartpaySignatureTimestamp) {
            $body = $request->getBody();
            $parsedBody = $request->getParsedBody();

            $calculatedSignature = hash_hmac('sha256', $smartpaySignatureTimestamp . "." . $body, $base62->decode($secret));


            if ($smartpaySignature == $calculatedSignature) {
                return $response;

        return $response->withStatus(400);
    } else {
        return $response->withStatus(404);

import os

from flask import Flask, request
from smartpay import Smartpay

SECRET_KEY = os.environ.get('SECRET_KEY', '<YOUR_SECRET_KEY>')
PUBLIC_KEY = os.environ.get('PUBLIC_KEY', '<YOUR_PUBLIC_KEY>')

smartpay = Smartpay(SECRET_KEY, public_key=PUBLIC_KEY)

app = Flask(__name__, static_url_path='')

root = '../client/build'

@app.route("/webhooks", methods=['POST'])
def webhooks():
    signature = request.headers['smartpay-signature']
    verified = smartpay.verify_webhook_signature(data=request.get_data(), secret=SIGNING_SECRET, signature=signature)

    if verified:
        print("processing webhook event")
        return ''

    return '', 400
require 'sinatra'
require 'base_x'

set :port, 3000


helpers do
  def request_headers
    env.inject({}){|acc, (k,v)| acc[$1.downcase] = v if k =~ /^http_(.*)/i; acc}

post '/webhooks' do
  content_type :json

  signature = request_headers["smartpay_signature"]
  signature_timestamp = request_headers["smartpay_signature_timestamp"]

  raw_body =
  body = JSON.parse raw_body
  p BaseX::Base62ULD.decode(secret).bytes

  hmac =,
  hmac.update signature_timestamp
  hmac.update '.'
  hmac.update raw_body
  calculated_signature = hmac.hexdigest

  p request_headers
  p body
  p calculated_signature

  if signature == calculated_signature
    return '', 200

  return '', 400
rescue => err
  if err.respond_to?(:response)
    raise err

Built-in retries

Smartpay webhooks have built-in retry for 3xx, 4xx, or 5xx response status codes. If you respond to a webhook request with anything other than 200, Smartpay will keep retrying the request with an exponential backoff.

4. Register your webhook endpoint

Register your newly created webhook endpoint with Smartpay to receive the events you want to subscribe to. You can do this by using the create a webhook API endpoint.

5. Secure your webhooks (recommended)

Use webhook signatures to verify that Smartpay generated a webhook request and that it didn’t come from a server acting like Smartpay.

Smartpay signs the webhook events it sends to your endpoints by including a signature in each event’s Smartpay-Signature header. This allows you to verify that the events were sent by Smartpay and not by a third party. You can verify signatures either using our SDKs, or manually using your own solution.

Before you can verify signatures, you need to retrieve your endpoint’s signingSecret. Smartpay generates a unique signingSecret for each endpoint and returns it to you when you register your webhook endpoint (see step 4) or retrieve a previously registered webhook endpoint.