Preview Leffel Consulting Payments is in active development and migrating to Finix — the API isn't publicly live yet, and some examples still reflect the outgoing build. Request early access →

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

POST → 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.

Auth: 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.

Request
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... }
Response
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"]));
}
Try it — the interactive sandbox console is coming soon.

Processor webhook (inbound)

POST /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.

Auth: Stripe-Signature
Response
{ "received": true, "forwarded": true }
Try it — the interactive sandbox console is coming soon.