API Reference
Webhooks & callbacks
Leffel Consulting Payments receives processor webhooks, updates the ledger, and forwards signed events to your application. Verify the LC-Signature header, then react to payment lifecycle events.
Events forwarded to your callback
-
payment_intent.succeeded— Payment completed — safe to fulfill. -
payment_intent.processing— Async payment (e.g. ACH) is pending — do not fulfill yet. -
payment_intent.payment_failed— Payment failed. -
payment_intent.amount_capturable_updated— A manual-capture authorization is ready to capture. -
payment_intent.canceled— The payment was canceled. -
charge.refunded— A refund was issued (full or partial). -
charge.dispute.created— A dispute/chargeback was opened. -
charge.dispute.closed— A dispute was resolved (won/lost). -
account.updated— A merchant’s onboarding/capability state changed.
Receive a signed event
→ your callback URL We POST the raw event JSON to the callback URL configured for your account. Verify the LC-Signature header before trusting the payload, then update your order and return 2xx. Deliveries retry up to 8 times with exponential backoff, then dead-letter. Deduplicate on LC-Event-Id.
Signature (LC-Signature) Returns
LC-Signature header optional t=<unix>,v1=<hex HMAC-SHA256 of "{t}.{rawBody}" using your callback signing secret>.
LC-Event-Type header optional The event type, e.g. payment_intent.succeeded, account.updated.
LC-Event-Id header optional The event id (evt_…). Deduplicate on this.
POST /your/callback HTTP/1.1
Content-Type: application/json
LC-Signature: t=1700000000,v1=8f3c...e1
LC-Event-Type: payment_intent.succeeded
LC-Event-Id: evt_1Nxyz...
{ ...raw event JSON... } static bool Verify(string secret, string header, string rawBody, TimeSpan tolerance)
{
var parts = header.Split(',').Select(p => p.Split('=', 2))
.ToDictionary(p => p[0], p => p[1]);
var t = long.Parse(parts["t"]);
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > tolerance.TotalSeconds)
return false;
var mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret),
Encoding.UTF8.GetBytes($"{t}.{rawBody}"));
var expected = Convert.ToHexString(mac).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(parts["v1"]));
} const crypto = require("crypto");
function verify(secret, header, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map(kv => kv.split("=")));
const t = Number(parts.t);
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret)
.update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
} Processor webhook (inbound)
/webhooks/stripe The endpoint Stripe calls directly. You do not call this — it is listed for completeness. Signatures are verified, events are de-duplicated, the ledger is updated, and events are forwarded to your callback URL.
Stripe-Signature { "received": true, "forwarded": true }