Skip to main content

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.

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)
}
}