Skip to content
RugWard
☰ Menü

RugWard Pay

Webhook'lar ve imza doğrulama

Her fatura durum değişikliğinde imzalı bir HTTPS bildirimi alın, bunu curl, Node.js, Python veya PHP ile doğrulayın ve yeniden denemeleri ve kopyaları güvenle yönetin.

Bu sayfada

Webhook'lar nasıl çalışır#

Bir faturanın durumu değiştiğinde RugWard, uç noktanıza JSON gövdeli bir POST gönderir. Her istek imzalama sırrınızla (signing secret) imzalanır; böylece isteğin RugWard'dan geldiğini ve değiştirilmediğini kanıtlayabilirsiniz.

Webhook ayarları
Webhook kutusu: uç nokta URL'si, New signing secret, Send test ping ve teslimat günlüğü

Kurulum#

  1. Panelde uç noktanızı Webhook alanına girin ve Save'e tıklayın.
  2. İmzalama sırrı (whsec_ ve ardından 40 karakter) yalnızca bir kez gösterilir. Sunucunuzda, API anahtarının yanında saklayın.
  3. Send test ping'e tıklayın. Panel, uç noktanızın döndürdüğü HTTP durumunu gösterir.
  4. Kutunun altındaki teslimat günlüğü her olayı, sonucunu ve deneme sayısını gösterir.

Uç nokta kuralları:

  • yalnızca https://, standart portta (:8443 yok), en fazla 500 karakter;
  • herkese açık bir ana makine adı: IP adresleri, localhost ve özel ağlar reddedilir;
  • 10 saniye içinde herhangi bir 2xx durumuyla yanıt verin. Yönlendirmeler izlenmez: 3xx yanıtı başarısızlık sayılır.

New signing secret sırrı hemen değiştirir; yenisini hemen devreye alın (eski sırla yapılan imzalar anında geçersiz olur). Boş bir URL kaydetmek webhook'ları kapatır.

Olaylar#

OlayGönderildiği durum
invoice.pendingbir ödeme görüldü, onaylar veya kalan tutar bekleniyor
invoice.paidtamamen ödendi ve onaylandı: gönderin
invoice.overpaid%1'den fazla fazlasıyla ödendi: gönderin
invoice.underpaidkur kilidi, onaylanan tutar yetersizken sona erdi
invoice.latesüre dolduktan sonra bir ödeme geldi
invoice.expiredödeme olmadan süresi doldu
invoice.canceledsiz iptal ettiniz
pingpaneldeki Send test ping düğmesi

Her olay fatura başına en fazla bir kez tetiklenir. Test faturaları asla webhook göndermez.

İstek#

Webhook request (what your endpoint receives)
POST /rugward-webhook HTTP/1.1
Host: shop.example
Content-Type: application/json
User-Agent: RugWard-Pay-Webhook/1.0
RugWard-Event-Id: Zx3kQ8pL2mN7vR1tY5wB9c:paid
RugWard-Event: invoice.paid
RugWard-Signature: t=1793324105,v1=5f0c3d0e8b1a…(64 hex characters)
Body: invoice.paid
{
  "id": "Zx3kQ8pL2mN7vR1tY5wB9c:paid",
  "type": "invoice.paid",
  "created": 1793324100,
  "data": {
    "invoice": {
      "id": "Zx3kQ8pL2mN7vR1tY5wB9c",
      "status": "paid",
      "amountUsd": "25.00",
      "orderId": "1234",
      "description": "1 x T-shirt, size M",
      "redirectUrl": "https://shop.example/thanks?order=1234",
      "createdAt": 1793323282069,
      "expiresAt": 1793326882069,
      "paidAt": 1793324100512,
      "assets": ["BTC", "ETH-8453", "USDC-8453"],
      "test": false,
      "quotes": [{ "id": 4711, "asset": "USDC-8453", "amount": "25.000629", "rateUsd": "1", "address": "0x…", "expiresAt": 1793325082069 }],
      "payments": [{ "txid": "0x…", "asset": "USDC-8453", "amount": "24.875626", "confirmations": 30, "required": 30, "confirmed": true, "seenAt": 1793324012000, "explorer": "https://basescan.org/tx/0x…" }]
    }
  }
}
Body: test ping (dashboard button)
{ "id": "ping:1793324000000", "type": "ping", "created": 1793324000, "data": {} }
  • id, <invoice id>:<status> biçimindedir ve RugWard-Event-Id başlığında da bulunur: kopyaları yok saymak için kullanın.
  • created Unix saniyesi cinsindendir; data.invoice, GET /api/pay/merchant/invoices/:id çağrısının döndürdüğü nesnenin aynısıdır.

İmzayı doğrulayın#

Başlık şöyle görünür: RugWard-Signature: t=1793324100,v1=5f2c… (64 onaltılık karakter).

  1. İstek gövdesini, alındığı haliyle ham olarak okuyun (herhangi bir JSON ayrıştırmasından önce).
  2. HMAC-SHA256(secret, t + "." + raw_body) değerini hesaplayın ve onaltılık olarak kodlayın.
  3. v1 ile sabit zamanlı karşılaştırın.
  4. t saatinizden 5 dakikadan fazla farklıysa isteği reddedin (tekrar saldırılarını önler).
  5. Ancak bundan sonra JSON'u ayrıştırın.

JSON'u önce ayrıştırıp yeniden serileştirmek baytları değiştirir ve imzayı bozar. İmzayı her zaman ham gövde üzerinde kontrol edin.

Node.js (Express): verify, then process
import crypto from "node:crypto";
import express from "express";

const SECRET = process.env.RUGWARD_WEBHOOK_SECRET; // whsec_…
const app = express();

// Use the RAW body: the signature covers the exact bytes RugWard sent.
app.post("/rugward-webhook", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(req.get("RugWard-Signature") || "");
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return res.sendStatus(400);
  const want = crypto.createHmac("sha256", SECRET).update(`${m[1]}.${raw}`).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(want), Buffer.from(m[2]))) return res.sendStatus(400);

  const event = JSON.parse(raw);
  // De-duplicate: the same event id can arrive more than once.
  // if (alreadyProcessed(event.id)) return res.sendStatus(200);
  if (event.type === "invoice.paid" || event.type === "invoice.overpaid") {
    const inv = event.data.invoice;
    // markOrderPaid(inv.orderId, inv.id);  // ship only now
  }
  res.sendStatus(200); // any 2xx = delivered
});
app.listen(3000);
Python (Flask)
import hashlib, hmac, json, os, re, time
from flask import Flask, abort, request

SECRET = os.environ["RUGWARD_WEBHOOK_SECRET"].encode()  # whsec_…
app = Flask(__name__)

@app.post("/rugward-webhook")
def rugward_webhook():
    raw = request.get_data()  # raw bytes, exactly as sent
    m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", request.headers.get("RugWard-Signature", ""))
    if not m or abs(time.time() - int(m.group(1))) > 300:
        abort(400)
    want = hmac.new(SECRET, m.group(1).encode() + b"." + raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(want, m.group(2)):
        abort(400)
    event = json.loads(raw)
    if event["type"] in ("invoice.paid", "invoice.overpaid"):
        inv = event["data"]["invoice"]
        # mark_order_paid(inv["orderId"], inv["id"])  # de-duplicate on event["id"]
    return "", 200
PHP 8
<?php
$secret = getenv("RUGWARD_WEBHOOK_SECRET"); // whsec_…
$raw = file_get_contents("php://input");
$sig = $_SERVER["HTTP_RUGWARD_SIGNATURE"] ?? "";
if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $sig, $m) || abs(time() - (int)$m[1]) > 300) {
    http_response_code(400); exit;
}
$want = hash_hmac("sha256", $m[1] . "." . $raw, $secret);
if (!hash_equals($want, $m[2])) { http_response_code(400); exit; }

$event = json_decode($raw, true);
if (in_array($event["type"], ["invoice.paid", "invoice.overpaid"], true)) {
    $inv = $event["data"]["invoice"];
    // mark_order_paid($inv["orderId"], $inv["id"]); // de-duplicate on $event["id"]
}
http_response_code(200);

Uç noktanızı elle test etmek için bir gövdeyi kendiniz imzalayın:

Shell (openssl): compute a signature to test your endpoint
SECRET='whsec_…'            # your signing secret
BODY='{"id":"ping:1","type":"ping","created":1793324000,"data":{}}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -X POST https://shop.example/rugward-webhook \
  -H "Content-Type: application/json" \
  -H "RugWard-Event: ping" \
  -H "RugWard-Signature: t=$T,v1=$SIG" \
  --data "$BODY"

Yeniden denemeler ve kopyalar#

Uç noktanız zaman aşımına uğrarsa veya 2xx ile yanıt vermezse RugWard yeniden dener: toplamda en fazla 4 deneme, yaklaşık 30 saniye, 2 dakika ve 8 dakika sonra. Bundan sonra olay, teslimat günlüğünde başarısız olarak işaretlenir. Bu nedenle işleyiciniz:

  • hızla 2xx ile yanıt vermeli, yavaş işleri sonra yapmalı;
  • idempotent olmalı: RugWard-Event-Id değerini saklayın ve zaten işlediğiniz olayları atlayın;
  • sıraya bağlı olmamalı: şüphe durumunda faturayı API ile yeniden okuyun.

Webhook'lar hızlı bir sinyaldir, tek sinyal değildir. Sunucunuz bir süre kapalı kaldıysa son faturaları API ile listeleyin ve durumları karşılaştırın.

Sorun giderme#

BelirtiOlası neden
"Webhook URL must be https:// with a public host name"bir IP adresi, özel bir port, http:// veya dahili bir ana makine adı
ping HTTP 301/302/308 gösteriyorsunucunuz yönlendiriyor (örneğin http→https veya eksik sondaki eğik çizgi); son URL'yi kullanın
ping 405 gösteriyorrota POST kabul etmiyor
imza hiç eşleşmiyorgövde hash'lemeden önce ayrıştırıldı veya yeniden kodlandı, yanlış sır kullanılıyor (New signing secret sonrası) veya sunucu saati yanlış
test faturaları için olay yokbeklenen durum: test faturaları asla webhook göndermez

Son güncelleme: 30 Ekim 2026

Webhook'lar ve imza doğrulama · RugWard Dokümantasyon | RugWard