SettleFlow API

Webhooks

Receive asynchronous E-PRO-formatted notifications for payment events

Overview

SettleFlow notifies your server asynchronously when a payment, refund or chargeback reaches a final state. In V1, webhook delivery is per-payment: you supply a CallbackUrl on each POST /v1/payment/direct request, and SettleFlow posts to that URL.

The delivered payload uses the E-PRO "standard response" shape, so it can plug straight into an existing E-PRO integration.

Configuring the callback URL

There is no dashboard step in V1 — set CallbackUrl on each payment:

curl -X POST https://api.settleflow.io/v1/payment/direct \
  -H "epro-api-key: sk_test_..." \
  -d '{
    "Amount": "4999",
    "Uid": "customer-42",
    "Tid": "order-2026-001",
    "Email": "jane@example.com",
    "CardNumber": "4111111111111111",
    "CardMonth": "12",
    "CardYear": "2028",
    "CardCVV": "123",
    "CallbackUrl": "https://your-shop.com/webhooks/settleflow"
  }'

Requirements:

  • HTTPS only in production.
  • Must respond with HTTP 2xx within 10 seconds.
  • Should be idempotent — webhooks may be delivered more than once.

Delivered events

V1 delivers these event types, all serialized to E-PRO format:

Payment events

Domain eventE-PRO OperationTypeE-PRO Status
ATTEMPT_CAPTUREDpaymentcaptured
ATTEMPT_AUTHORIZEDpaymentpending
ATTEMPT_FAILEDpaymentfailed

Refund events

Domain eventE-PRO OperationTypeE-PRO Status
REFUND_SUCCEEDEDrefundcaptured
REFUND_FAILEDrefundfailed

Dispute / chargeback events

Domain eventE-PRO OperationTypeE-PRO Status
DISPUTE_OPENEDchargebackchargeback
DISPUTE_WONchargebackchargeback_reversed
DISPUTE_LOSTchargebackchargeback

Payload

{
  "Code": 0,
  "Result": {
    "OperationType": "payment",
    "Status": "captured",
    "Tid": "order-2026-001",
    "Reference": "pr_abc123",
    "Amount": 49.99,
    "UserId": "customer-42",
    "Message": "Payment was successful",
    "Date": "2026-04-22 14:30:47"
  }
}

Field mapping

FieldSource
OperationTypepayment, refund, or chargeback
StatusSee the tables above
TidYour Tid from the original payment request
ReferenceSettleFlow payment request ID
AmountAmount in major currency units (e.g. 49.99)
UserIdThe Uid supplied on the original payment
MessageHuman-readable outcome
DateServer time, YYYY-MM-DD HH:mm:ss (UTC)

HTTP headers

HeaderDescription
Content-Typeapplication/json
User-AgentSettleFlow/1.0
X-SettleFlow-SignatureHMAC-SHA256 signature (see below)
X-SettleFlow-TimestampUnix timestamp (seconds) of the delivery
X-SettleFlow-EventDomain event type (e.g. ATTEMPT_CAPTURED)

Signature verification

Every webhook is signed with your webhook secret (starts with whsec_). Verify signatures on every request — it's the only way to confirm a payload actually came from SettleFlow.

Signature scheme

signature = "sha256=" + hex( HMAC-SHA256(webhookSecret, timestamp + "." + rawBody) )

Where:

  • webhookSecret — your app's webhook secret (whsec_...).
  • timestamp — value from the X-SettleFlow-Timestamp header.
  • rawBody — the request body as a raw string (do not reserialize the parsed JSON).

Node.js example

import crypto from "node:crypto";

function verifyWebhook(secret, signature, timestamp, rawBody) {
  // 1. Reject timestamps older than 5 minutes to prevent replay.
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(timestamp)) > 300) {
    throw new Error("Webhook timestamp too old");
  }

  // 2. Compute the expected signature.
  const data = `${timestamp}.${rawBody}`;
  const expected = crypto.createHmac("sha256", secret).update(data).digest("hex");

  // 3. Timing-safe compare.
  const provided = signature.replace(/^sha256=/, "");
  const a = Buffer.from(provided, "hex");
  const b = Buffer.from(expected, "hex");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    throw new Error("Invalid webhook signature");
  }
}

// Express / Hono example (requires raw body middleware)
app.post("/webhooks/settleflow", (req, res) => {
  try {
    verifyWebhook(
      process.env.SETTLEFLOW_WEBHOOK_SECRET,
      req.headers["x-settleflow-signature"],
      req.headers["x-settleflow-timestamp"],
      req.rawBody,
    );
  } catch {
    return res.status(401).send("Invalid signature");
  }

  const { Result } = JSON.parse(req.rawBody);
  // …handle Result.Status…
  res.status(200).send("OK");
});

PHP example

<?php

function verify_webhook(string $secret, string $signature, string $timestamp, string $rawBody): bool {
    if (abs(time() - (int)$timestamp) > 300) {
        return false; // replay
    }
    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    $provided = preg_replace('/^sha256=/', '', $signature);
    return hash_equals($expected, $provided);
}

$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_SETTLEFLOW_SIGNATURE'] ?? '';
$ts  = $_SERVER['HTTP_X_SETTLEFLOW_TIMESTAMP'] ?? '';

if (!verify_webhook(getenv('SETTLEFLOW_WEBHOOK_SECRET'), $sig, $ts, $raw)) {
    http_response_code(401);
    exit('Invalid signature');
}

$payload = json_decode($raw, true);
// …handle $payload['Result']…
http_response_code(200);
echo 'OK';

Retry policy

A webhook delivery is considered failed when your endpoint returns a non-2xx status, times out after 10 seconds, or is unreachable. SettleFlow retries up to 3 times with increasing delays:

AttemptDelay after previous
1st retry5 seconds
2nd retry1 minute
3rd retry5 minutes

After the third retry, the webhook is marked FAILED. The merchant dashboard exposes a delivery log where you can inspect payloads and response status codes.

Best practices

  1. Return 2xx fast. Acknowledge the request, then do the heavy work asynchronously.
  2. Verify signatures and timestamps. Reject anything older than 5 minutes.
  3. Deduplicate on Reference. A payment's Reference (payment request ID) is stable across retries.
  4. Treat the webhook as source of truth. Do not finalize orders solely on the synchronous response of /v1/payment/direct when 3DS is involved — wait for the webhook or the status endpoint to confirm.
  5. Keep your webhook secret secret. Rotate it if you suspect a leak.

On this page