Skip to main content

Requirements

  • HTTPS endpoint with a stable URL.
  • Accept POST with Content-Type: application/json.
  • Return 2xx quickly (store → ack → process async).
Use Verify signature if you want to validate validation_hash.

Example handlers

Node.js (Express)

import crypto from "node:crypto";
import express from "express";

const app = express();
app.use(express.json({ limit: "256kb" }));

app.post("/webhooks/payments", async (req, res) => {
  const body = req.body;

  // 1) Basic validation
  if (!body?.status || !body?.results?.payment_uuid) {
    return res.status(400).send("Invalid payload");
  }

  // 2) Verify integrity (optional but recommended)
  // compute validation_hash as shown in Verify Signature page

  // 3) Idempotent persist
  await upsertByPaymentUuid(body.results.payment_uuid, body);

  // 4) Respond fast
  return res.status(202).send("Accepted");
});

app.listen(3000);

Python (FastAPI)

from fastapi import FastAPI, Request, HTTPException
app = FastAPI()

@app.post("/webhooks/payments")
async def webhook(req: Request):
  body = await req.json()
  if "status" not in body or "results" not in body:
    raise HTTPException(400, "Invalid payload")

  # verify hash (optional), then persist idempotently
  save_or_update(body["results"]["payment_uuid"], body)

  return {"ok": True}

PHP (Laravel)

Route::post('/webhooks/payments', function (Illuminate\Http\Request $request) {
    $data = $request->json()->all();
    if (!isset($data['status'], $data['results']['payment_uuid'])) {
        return response('Invalid payload', 400);
    }

    // verify hash (optional), then idempotent save
    upsert_by_payment_uuid($data['results']['payment_uuid'], $data);

    return response()->json(['ok' => true], 202);
});