You can start receiving event notifications in your app using the steps below:
- Identify the events you want to monitor and the event payloads to parse.
- Create a webhook endpoint as an HTTP endpoint (URL) on your local server.
- Handle requests from Smartpay by parsing each event object and returning 200 response status codes.
- Register your publicly accessible HTTPS URL with Smartpay using the create a webhook endpoint API endpoint .
- Secure your webhooks (recommended).
- Deploy your webhook endpoint so it is a publicly accessible HTTPS URL.
- 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 (
"bytes"
"encoding/json"
"log"
"io/ioutil"
"net/http"
"github.com/smartpay-co/sdk-go"
)
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)
w.WriteHeader(http.StatusBadRequest)
return
}
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)
return
}
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)
return
}
log.Println(string(prettyJSON.Bytes()))
} else {
log.Printf("Body: No Body Supplied\n")
}
w.Write([]byte("OK"))
}
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("0.0.0.0:3000", mux)
log.Fatal(err)
}
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();
app.use(
express.json({
verify: Smartpay.expressWebhookMiddleware(secret),
})
);
app.post("/webhooks", async (req, res, next) => {
const event = req.body;
const signature = req.headers["smartpay-signature"];
const calculatedSignature = req.headers["calculated-signature"];
console.log(req.headers);
console.log(event);
console.log(calculatedSignature);
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.delete(key);
res.send("");
return;
}
log.set(key, { count: existing.count + 1 });
}
next(createError.BadRequest());
});
app.listen(3000, "0.0.0.0", () =>
console.log("Node server listening on port 3000!")
);
<?php
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));
print_r($calculatedSignature);
print_r($headers);
print_r($parsedBody);
if ($smartpaySignature == $calculatedSignature) {
return $response;
}
}
return $response->withStatus(400);
} else {
return $response->withStatus(404);
}
});
$app->run();
import os
from flask import Flask, request
from smartpay import Smartpay
SIGNING_SECRET = 'YOUR_SIGNING_KEY'
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
secret = 'YOUR_SIGNING_KEY'
helpers do
def request_headers
env.inject({}){|acc, (k,v)| acc[$1.downcase] = v if k =~ /^http_(.*)/i; acc}
end
end
post '/webhooks' do
content_type :json
signature = request_headers["smartpay_signature"]
signature_timestamp = request_headers["smartpay_signature_timestamp"]
raw_body = request.body.read
body = JSON.parse raw_body
p BaseX::Base62ULD.decode(secret).bytes
hmac = OpenSSL::HMAC.new(BaseX::Base62ULD.decode(secret), OpenSSL::Digest::SHA256.new)
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
end
return '', 400
rescue => err
if err.respond_to?(:response)
err.response.body
else
raise err
end
end
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.