Paymish Docs
Webhooks
- Paymish
- Webhooks
Webhooks
Receive real-time Paymish events on your backend
Paymish sends HTTP POST notifications when important events happen in your account, such as completed transactions, failed transfers, or identity updates.
- Go to Dashboard → Settings → Webhooks.
- Add a publicly accessible HTTPS endpoint such as
https://yourapp.com/webhooks/paymish. - Select the events you want to receive and save the endpoint.
- Copy the generated Webhook Secret and store it securely for signature verification.
Important: never trust incoming webhook payloads until the signature is verified.
Event
Description
transaction.successfulA payment or transfer completed successfully.
transaction.failedA payment or transfer attempt failed.
transfer.pendingA transfer has been queued and is awaiting processing.
account.verifiedA user verification check was completed successfully.
wallet.fundedA wallet received new funds.
Every webhook includes a consistent outer envelope. The nested data object changes depending on the event type.
Example payload - transaction.successful
{
"event": "transaction.successful",
"timestamp": "2024-06-01T14:22:10Z",
"data": {
"id": "txn_abc123",
"amount": 5000,
"currency": "GHS",
"reference": "ref_xyz789",
"status": "success",
"customer": {
"email": "customer@example.com"
}
}
}
Paymish signs every webhook using HMAC-SHA256. Verify the signature before you parse or process the body.
Python (Django)
import hmac
import hashlib
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
import json
WEBHOOK_SECRET = "your_webhook_secret"
@csrf_exempt
def paymish_webhook(request):
signature = request.headers.get("X-Paymish-Signature", "")
payload = request.body
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, signature):
return HttpResponseBadRequest("Invalid signature")
event = json.loads(payload)
event_type = event.get("event")
if event_type == "transaction.successful":
pass
elif event_type == "transaction.failed":
pass
return HttpResponse(status=200)
Node.js (Express)
const crypto = require("crypto");
const express = require("express");
const app = express();
const WEBHOOK_SECRET = "your_webhook_secret";
app.post("/webhooks/paymish", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-paymish-signature"];
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(400).send("Invalid signature");
}
const event = JSON.parse(req.body);
switch (event.event) {
case "transaction.successful":
break;
case "transaction.failed":
break;
}
res.status(200).send("OK");
});
PHP
<?php
$webhookSecret = "your_webhook_secret";
$payload = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_PAYMISH_SIGNATURE"] ?? "";
$expected = hash_hmac("sha256", $payload, $webhookSecret);
if (!hash_equals($expected, $signature)) {
http_response_code(400);
exit("Invalid signature");
}
$event = json_decode($payload, true);
switch ($event["event"]) {
case "transaction.successful":
break;
case "transaction.failed":
break;
}
http_response_code(200);
echo "OK";
- Verify the
X-Paymish-Signatureheader before processing any payload. - Return
200quickly and offload heavier work to a queue or background worker. - Handle duplicate events idempotently using a stable event identifier.
- Log failed processing attempts and retry them safely.
- Accept webhook traffic over HTTPS only.