Webhooks
Webhooks push events from MCA Manager into your own systems in real time. Manage endpoints under Settings → Webhooks (requires the edit company settings permission).
Add an endpoint
Provide the destination URL and the events you want (or * for all).
The URL must be a public https address; private networks, localhost and
cloud metadata addresses are refused, and the address is checked again before
every delivery. Each endpoint gets a signing secret, shown once, used to sign
every delivery.
Events
| Event | When |
|---|---|
| deal.created | A deal was created (staff, API, portal or email intake). |
| deal.state_changed | A deal moved to a new state (funded, closed, defaulted…), from any path: screen, API, automation, early payoff. |
| merchant.created | A merchant record was created. |
| lead.created | A lead was created. |
| lead.status_changed | A lead's status changed. |
| submission.status_changed | A funder submission moved to a new pipeline status. |
| payment.paid | A payment was marked paid — by staff, by a processor callback, or through the API. |
| payment.bounced | A payment was returned; carries the NACHA return code when known. |
| esign.signed | One party signed a document sent for signature. |
| esign.completed | Every party signed; carries the filed document id and url. |
| esign.declined | A signer declined; carries their reason. |
| webhook.test | Sent by the Send test button. |
The same list, with example payloads, is returned by GET /api/v1/webhook-endpoints.
Envelope
{ "id": "whd_9123", "event": "payment.paid", "version": 1, "occurredAt": "2026-09-22T14:03:11.000Z", "data": { "paymentId": 9001, "dealId": 1042, "amount": "425.00", "source": "processor" } }
id is stable per delivery (a redelivery gets a new id); use it to de-duplicate on
your side. version is bumped only for breaking changes to the envelope or a
payload. Headers X-Webhook-Id and X-Webhook-Event repeat the id and event.
Verifying signatures
Each delivery is a JSON POST with headers:
X-Timestamp- unix-style timestamp (seconds)X-Signature- HMAC-SHA256, hex
Compute the signature over the string timestamp + "." + body using your
endpoint’s secret and compare with X-Signature in constant time. Reject if they
don’t match or the timestamp is more than five minutes old.
Rotating the secret: Rotate secret on an endpoint (or POST /api/v1/webhook-endpoints/{id}?action=rotate) issues a new secret. For 24 hours
every delivery also carries X-Signature-Previous, signed with the old secret, so
you can switch at your own pace.
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = createHmac("sha256", SECRET).update(`${ts}.${rawBody}`).digest("hex");
const ok = expected.length === sig.length && timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
import hmac, hashlib, time
def verify(secret: str, ts: str, raw_body: bytes, sig: str) -> bool:
if abs(time.time() - int(ts)) > 300: return False
expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
Delivery, retries and the log
Deliveries are at-least-once. Each attempt times out after 10 seconds; a failed attempt is retried five times over roughly eight minutes (15 s, 30 s, 1 m, 2 m, 4 m). Return any 2xx quickly and do the work afterwards.
Open Deliveries on an endpoint to see every attempt: time, event, status, HTTP response, tries, duration, the first part of your response body, and the payload. Redeliver sends a past payload again as a new delivery. Rows are kept for 30 days.
An endpoint that fails 20 deliveries in a row is paused and the admins are notified; fix the receiver, then Re-enable it. Turning an endpoint off yourself is not a pause and is never notified.
Managing endpoints through the API
With an API key that carries the Webhooks API scope:
GET/POST /api/v1/webhook-endpoints, GET/PATCH/DELETE /api/v1/webhook-endpoints/{id},
POST /api/v1/webhook-endpoints/{id}?action=rotate. The list call also returns the
event catalogue with example payloads.
Inbound webhooks
POST /api/webhooks/inbound/<source> accepts signed payloads from your systems
(bank statements, credit profiles, disclosures, legal updates) using the same
HMAC scheme with the inboundWebhookSecret setting. Send an eventId (or id)
with each payload: a repeat of the same id within 30 days is acknowledged and not
applied again.
Can’t find what you need? Return to the app or contact your administrator.