Skip to content
NIXPAYdocs

Webhooks

When a payment's status changes, NIXPAY POSTs a signed JSON event to your payment link's webhook_url.

Payload

code
{
  "id": "evt_...",
  "type": "payment_link.paid",
  "created_at": "2026-07-04T20:00:00Z",
  "data": {
    "payment_link_id": "pl_...",
    "transaction_id": "txn_...",
    "status": "paid",
    "amount": { "value": "49.95", "currency": "EUR" },
    "metadata": { "order_ref": "1234" }
  }
}

Verify the signature

Every delivery includes a signature header:

code
Nixpay-Signature: t=1783108324,v1=6df2c5...e9

v1 is HMAC-SHA256(secret, "<t>.<raw_body>") (hex), where secret is your tenant webhook secret and <t> is the header timestamp. Recompute it over the raw request body, compare in constant time, and reject stale timestamps.

Node

code
import crypto from 'node:crypto'

export function verify(secret, header, rawBody, toleranceSec = 300) {
  const parts = {}
  for (const seg of header.split(',')) {
    const [k, v] = seg.split('=')
    if (k && v !== undefined) parts[k.trim()] = v.trim()
  }
  const t = Number(parts.t)
  if (!Number.isFinite(t) || t === 0) return false
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  const a = Buffer.from(expected)
  const b = Buffer.from(parts.v1 ?? '')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

Go

code
func Verify(secret, header string, body []byte, tol int64) bool {
    var t, v1 string
    for _, p := range strings.Split(header, ",") {
        kv := strings.SplitN(p, "=", 2)
        if len(kv) == 2 && kv[0] == "t" { t = kv[1] }
        if len(kv) == 2 && kv[0] == "v1" { v1 = kv[1] }
    }
    ts, _ := strconv.ParseInt(t, 10, 64)
    if ts == 0 || abs(time.Now().Unix()-ts) > tol { return false }
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(fmt.Sprintf("%s.%s", t, body)))
    return hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(v1))
}

Delivery semantics

Return a 2xx to acknowledge. Failed deliveries are retried with exponential backoff and eventually dead-lettered. Deliveries may be duplicated — dedupe on the top-level event id.