Webhooks

Add endpoints under Webhooks in the merchant panel. Fastaar POSTs a JSON payload when a payment reaches a terminal state, or when a customer record changes.

Events

Event Fires when
payment.completedA payment is verified and marked completed.
payment.failedA payment fails verification (e.g. duplicate TrxID, rejected by admin).
payment.expiredA payment's checkout window closes without a submitted TrxID.
payment.refundedA payment is fully refunded (in one call, or across several partial refunds) via the API or merchant panel.
payment.partially_refundedA payment is refunded for less than its full remaining balance.
customer.createdA new customer record is created.
customer.updatedAn existing customer record is updated.

Payment event payload

{
  "event":      "payment.completed",
  "created_at": "2026-06-12T14:31:05+06:00",
  "data": {
    "id":                     "01jxyz...",
    "amount":                 "500.00",
    "amount_due":             "505.00",   // includes cash-out charge if configured
    "refunded_amount":        "0.00",     // cumulative amount refunded so far
    "gateway_charge":         "5.00",     // actual charge applied (0.00 if none)
    "gateway_charge_type":    "fixed",    // "percentage", "fixed", or null
    "gateway_charge_value":   "5.00",     // configured rate; null if no charge
    "currency":               "BDT",
    "livemode":               true,
    "source":                 "api",
    "status":                 "completed",
    "invoice_number":         "ORDER-42",
    "customer_id":            null,       // the customer_id passed at creation, or set automatically via a payment link
    "provider":               "bkash",
    "payment_method":         "bKash (01512345678)",
    "customer_trx_id":        "8AB2C3D4EF",
    "customer_sender_number": "01512345678",
    "metadata":               {"order_id": "ORDER-42"},
    "failure_reason":         null,
    "verified_at":            "2026-06-12T14:31:05+06:00",
    "created_at":             "2026-06-12T14:30:00+06:00"
  }
}

Customer event payload

{
  "event":      "customer.created",
  "created_at": "2026-06-12T14:31:05+06:00",
  "data": {
    "id":         "01jxyz...",
    "name":       "Jane Doe",
    "email":      "jane@example.com",
    "phone":      "01512345678",
    "address":    "House 12, Road 3, Dhaka",
    "created_at": "2026-06-12T14:30:00+06:00"
  }
}

Verifying signatures

Each request carries an X-Fastaar-Event header and an X-Fastaar-Signature header of the form t=<timestamp>,v1=<hmac>, where v1 is an HMAC-SHA256 of "{t}.{raw_body}" using your webhook secret (shown in the merchant panel). Reject requests older than 5 minutes.

PHP:

$header = $_SERVER['HTTP_X_FASTAAR_SIGNATURE'];
$body = file_get_contents('php://input');

if (! preg_match('/^t=(?P<t>\d+),v1=(?P<v1>[a-f0-9]{64})$/', $header, $m)) {
    http_response_code(400); exit;
}

$valid = abs(time() - (int) $m['t']) <= 300
    && hash_equals(hash_hmac('sha256', "{$m['t']}.{$body}", $secret), $m['v1']);

Node.js:

const crypto = require('crypto');

function verify(header, rawBody, secret) {
  const m = Object.fromEntries(header.split(',').map(p => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(m.t)) > 300) return false;

  const expected = crypto.createHmac('sha256', secret)
    .update(`${m.t}.${rawBody}`)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m.v1));
}

Testing

Send a test delivery without making a real payment. In the merchant panel, open a webhook endpoint and click Send Test. Pick the event you want to simulate — the panel dispatches a realistic payload signed with your endpoint's secret, so you can confirm your server receives and verifies it correctly. The delivery is logged under Webhook Logs like any live event.

Retries

Respond with a 2xx status within 10 seconds. Failed deliveries are retried up to 5 times with exponential backoff: 1 min, 5 min, 30 min, 2 h, 12 h. After all attempts are exhausted the log is marked exhausted — you can inspect and manually redeliver from the merchant panel. Handle duplicates idempotently using data.id as the idempotency key.