Webhooks

Receive real-time notifications when events occur in your PayForge account.

How Webhooks Work

When an event occurs (e.g., a payment is captured), PayForge sends an HTTP POST request to your configured endpoint with the event data. Your endpoint should return a 2xx status to acknowledge receipt.

Retry Policy
5 attempts over 24h
Timeout
30 seconds
Signature
HMAC-SHA256

Webhook Payload

Example webhook payloadjson
{
  "id": "evt_01ABC...",
  "type": "payment.captured",
  "created_at": "2026-03-06T10:31:00Z",
  "data": {
    "id": "pay_01HXYZ...",
    "amount_cents": 5000,
    "currency": "USD",
    "status": "captured",
    "merchant_id": "mer_01ABC...",
    "fraud_score": 0.03
  }
}

Event Types

EventDescription
payment.initiatedPayment created and queued for processing
payment.authorizedNetwork authorization successful
payment.authorization_failedNetwork declined the authorization
payment.fraud_passedFraud check passed, payment cleared
payment.fraud_blockedFraud check blocked the payment
payment.capturedPayment captured and funds secured
payment.settledSettlement complete, funds transferred
payment.refundedFull refund processed
payment.partially_refundedPartial refund processed
payment.voidedAuthorization voided before capture
payment.dispute_openedDispute or chargeback opened
payment.dispute_resolvedDispute resolved

Signature Verification

Every webhook includes a X-PayForge-Signature header. Always verify signatures to ensure the webhook came from PayForge. The signature format is t=timestamp,v1=signature.

Verify webhook signature
def verify_webhook(payload, signature_header, secret)
  parts = signature_header.split(",").map { |p| p.split("=", 2) }.to_h
  timestamp = parts["t"]
  expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{payload}")

  # Timing-safe comparison prevents timing attacks
  Rack::Utils.secure_compare(expected, parts["v1"])
end

# Usage in a Rails controller
class WebhooksController < ApplicationController
  def receive
    payload = request.body.read
    sig = request.headers["X-PayForge-Signature"]

    unless verify_webhook(payload, sig, ENV["PAYFORGE_WEBHOOK_SECRET"])
      head :unauthorized
      return
    end

    event = JSON.parse(payload)
    # Handle the event...
    head :ok
  end
end

Retry Schedule

Failed deliveries are retried with exponential backoff:

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry (final)24 hours

Testing Webhooks

Use the test endpoint to send a sample webhook event to your configured URL:

Send test webhookbash
curl -X POST "https://api.payforge.dev/api/v1/webhooks/test" \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "payment.captured" }'

You can also view delivery history in the dashboard under Webhooks → Deliveries to debug failed deliveries and inspect payloads.