Webhooks

Paisr uses webhooks to notify your application about events that happen within your profile—such as completed payments, invoice updates, incoming transfers and more. Webhooks enable real-time updates without needing to constantly poll the API.

You can subscribe to the events that matter to you and build automations or workflows that react instantly when they occur.

How they work

Whenever an event occurs—like a payment being completed or a customer being created—Paisr creates an event object and sends it as an HTTP POST request to the url(s) you’ve configured.

Each event payload includes:

  • The event type (e.g. invoice.paid, customer.created)
  • The ID of the resource
{
    "id": "inv_123456",
    "event": "invoice.paid",
}

You can easily create, and manage webhooks from the Developers section in your Dashboard, or programatically using the Webhooks API.

Authenticating Webhooks

To verify that a webhook was sent by Paisr, each request includes the following headers:

  • X-PCB-Signature
  • X-PCB-Timestamp

The X-PCB-Signature header is an HMAC-SHA256 signature generated using your Webhook Secret. This helps prevent spoofed or replayed requests from being accepted.

To validate:

  1. Retrieve the X-PCB-Signature and X-PCB-Timestamp from the request headers.
  2. Compute the HMAC using your webhook secret.
  3. Compare it to the signature in the header.
signature = HMAC_SHA256(secret, timestamp + '.' + body)

The most important step is to always sign and verify the exact raw JSON — any re-stringification can alter the payload and invalidate the signature.

Below are some examples:

const crypto = require("crypto");

function verifyWebhook(req, secret) {
  const signature = req.header("X-PCB-Signature");
  const timestamp = req.header("X-PCB-Timestamp");
  const rawBody = req.rawBody; // Ensure middleware captures raw body

  const payload = `${timestamp}.${rawBody}`;
  const hmac = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(signature));
}
import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"net/http"
)

func verifyWebhook(r *http.Request, secret []byte, rawBody []byte) bool {
	signature := r.Header.Get("X-PCB-Signature")
	timestamp := r.Header.Get("X-PCB-Timestamp")

	message := append([]byte(timestamp+"."), rawBody...)
	mac := hmac.New(sha256.New, secret)
	mac.Write(message)
	expectedMAC := mac.Sum(nil)

	signatureBytes, err := hex.DecodeString(signature)
	if err != nil {
		return false
	}

	return hmac.Equal(expectedMAC, signatureBytes)
}
use hmac::{Hmac, Mac};
use sha2::Sha256;
use hex;

type HmacSha256 = Hmac<Sha256>;

fn verify_webhook(raw_body: &str, timestamp: &str, signature: &str, secret: &str) -> bool {
    let payload = format!("{}.{}", timestamp, raw_body);
    let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
    mac.update(payload.as_bytes());

    match hex::decode(signature) {
        Ok(signature_bytes) => mac.verify_slice(&signature_bytes).is_ok(),
        Err(_) => false,
    }
}
import hmac
import hashlib

def verify_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    payload = f"{timestamp}.{raw_body.decode('utf-8')}".encode('utf-8')
    expected_signature = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected_signature, signature)
function verifyWebhook($rawBody, $timestamp, $signature, $secret) {
    $payload = $timestamp . '.' . $rawBody;
    $expected = hash_hmac('sha256', $payload, $secret);
    return hash_equals($expected, $signature);
}

Always store and HMAC the raw JSON string (rather than re-stringifying after parsing). Differences in whitespace, or key ordering will break the signature verification.

Other unique headers include:

  • User Agent: PCB Events
  • Powered By: PAISR Technologies NV

Retries & Reliability

If your server fails to respond with a 2XX status, Paisr automatically retries the event using an exponential backoff strategy for up to 10 retries. You can track delivery attempts, failures, and retry schedules in the Dashboard or via the Webhooks API.

Monitoring

You can monitor the performance of your webhooks via the Dashboard or using the Webhooks API. These metrics help you monitor the health of your integration and identify misconfigurations or downtime quickly.

Best Practices

  • Always verify the webhook signature.
  • Avoid blocking your main application thread.
  • Log webhook receipts and failures for traceability.
  • Frequently monitor the performance of your webhooks.