Introduction
This page introduces you to how Webhooks work.
info
Please be aware that this endpoint requires a Manage Webhooks API Key.
Webhook event request format
webhook.json
{
"id": "b3252825-bb48-4a5e-8c99-69ea8e4db0d9",
"group_id": "5a7f4ef3-3d82-4606-9b27-ff50ad2789f3",
"webhook_id": "ea3455f8-5b1d-46f0-b08d-a9ce459c8a84",
"event_type": "transaction.initiate",
"data": {},
"created_at": "2024-07-28T15:11:16.975339432Z"
}
Possible data values
Transaction
Triggered by: transaction.initiate
, transaction.pending
, transaction.decline
, transaction.settlement
, transaction.void
, transaction.refund
,
transaction_webhook.json
{
"id": "b3252825-bb48-4a5e-8c99-69ea8e4db0d9",
"group_id": "5a7f4ef3-3d82-4606-9b27-ff50ad2789f3",
"webhook_id": "ea3455f8-5b1d-46f0-b08d-a9ce459c8a84",
"event_type": "transaction.initiate",
"data": {
"id": "c05861f0-6384-414d-b837-0ed71cefbbe5",
"group_id": "5a7f4ef3-3d82-4606-9b27-ff50ad2789f3",
"gateway": "revere_pay",
"processor_id": "<processor_id>",
"processor_name": "NO AFFILIATE",
"processor_type": "revere_pay",
"transaction_type": "sale",
"payment_method": "card",
"payment_details": {
"card": {
"brand": "Visa",
"first_six": "411111",
"last_four": "1111",
"customer_name": "James Testington",
"expiration_date": "26/11",
"avs": "not available",
"cvv": "no match",
"issued_country": "US"
}
},
"amounts": {
"requested_amount": 3000,
"authorized_amount": 3000,
"captured_amount": 3000,
"included_tax_amount": 300,
"included_shipping_amount": 1000
},
"currency": "USD",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sed lacus ac mi gravida euismod.",
"source": "api",
"order_id": "98765",
"po_number": "12345",
"email_receipt": false,
"email_address": "test@example.com",
"billing_address": {
"first_name": "James",
"last_name": "Testington",
"company": "James' Company",
"city": "Scranton",
"line_1": "Slough Avenue",
"line_2": "1725",
"subdivision": "PA",
"postal_code": "18505",
"country": "US",
"email": "test@example.com"
},
"shipping_address": {
"first_name": "James",
"last_name": "Testington",
"company": "James' Company",
"city": "Scranton",
"line_1": "Slough Avenue",
"line_2": "1725",
"subdivision": "PA",
"postal_code": "18505",
"country": "US"
},
"form_external_id": "",
"form_entry_id": "",
"subscription_id": "",
"plan_id": "",
"custom_fields": {},
"response_code": "SUCCESS",
"response_text": "confirmed",
"status": "pending",
"created_at": "2024-07-28T15:11:16.975339432Z"
},
"created_at": "2024-07-28T15:11:16.975339432Z"
}
Signature verification
Signature value will be passed in the X-Signature
header, it is hex encoded for the better text readability.
The request is signed with an RSA Private key and can be verified with the RSA Public key.
The RSA Public key can be found inside the Hub, but also available through the API in the public_key
field.
- Golang
- NodeJS
webhook_receiver.go
package main
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"io"
"log/slog"
"net/http"
)
// copiedPublicKey is the public key from the Hub
const copiedPublicKey = `-----BEGIN RSA PUBLIC KEY-----
<replace this with your key>
-----END RSA PUBLIC KEY-----
`
func main() {
srv := http.NewServeMux()
srv.HandleFunc("POST /receive-webhook", func(w http.ResponseWriter, r *http.Request) {
rawBody, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer r.Body.Close()
signatureHex := r.Header.Get("X-Signature")
signature, err := hex.DecodeString(signatureHex)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
pubParsedBlock, _ := pem.Decode([]byte(copiedPublicKey))
publicKey, err := x509.ParsePKCS1PublicKey(pubParsedBlock.Bytes)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
hashedBody := sha256.Sum256(rawBody)
if err := rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashedBody[:], signature); err != nil {
slog.Error("invalid webhook signature")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
})
slog.Info("starting server on 8080")
if err := http.ListenAndServe(":8080", srv); err != nil {
panic(err)
}
}
webhook_receiver.js
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();
function defaultContentTypeMiddleware(req, res, next) {
req.headers['content-type'] = req.headers['content-type'] || 'application/json';
next();
}
app.use(defaultContentTypeMiddleware);
app.use(bodyParser.json());
const publicKey = `-----BEGIN RSA PUBLIC KEY-----
<replace this with your key>
-----END RSA PUBLIC KEY-----
`;
app.post('/receive-webhook', async (req, res, next) => {
const rawBody = JSON.stringify(req.body);
const signatureHex = req.get('X-Signature');
const verifier = crypto.createVerify('sha256');
verifier.update(rawBody);
const result = verifier.verify(publicKey, signatureHex, 'hex');
if (!result) {
res.status(400);
return;
}
res.json(result);
});
app.listen(8080, () => {
console.log('Server running on port 8080');
});