← Back to appMCA Manager Helpmcamgr.com
Browse help topics

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

EventWhen
deal.createdA deal was created (staff, API, portal or email intake).
deal.updatedA deal's fields were edited (staff or API). changed names the fields; values are not repeated — fetch the deal.
deal.deletedA deal was deleted (only possible while nothing references it). A tombstone for updatedSince sync.
deal.state_changedA deal moved to a new state (funded, collections, closed, defaulted…).
merchant.createdA merchant record was created.
lead.createdA lead was created.
lead.status_changedA lead's status changed.
submission.status_changedA funder submission moved to a new pipeline status.
payment.createdA payment row was created: a pull scheduled, a fee charged, a payment recorded or imported (rows from history loads only when recent). A schedule rebuild sends one per new pull.
payment.cancelledA payment was cancelled (by staff, a payoff close-out, a schedule repair or rebuild). Kept on record; never collected.
payment.deletedA mistaken manual payment was deleted (its journal reversed). A tombstone for updatedSince sync.
payment.refundedMoney went back to the merchant (a refund was paid out); also sent as payment.paid with a negative credit amount.
payment.paidA payment was marked paid (staff, processor, import or API). type and direction tell a collection from a fee or a refund (a refund is a negative credit).
payment.bouncedA payment was returned; carries the NACHA return code when known.
merchant.bank_account_changedA merchant's bank account was added, edited, re-ordered or removed. Never carries account or routing numbers.
person.mergedTwo person records were merged; contacts on the dropped one now point at the kept one.
contract.generatedA contract requested through the API (POST /deals/{id}/contracts) was generated: a draft signature request is ready to review and send.
contract.failedA contract requested through the API could not be generated; carries the reason (no signers chosen, template archived, conversion failed…).
esign.sentA signature request went out to its signers (staff or API).
esign.voidedA signature request was withdrawn; its signer links stop working.
esign.signedOne party signed a document sent for signature.
esign.completedEvery party signed; carries the filed document id and url.
esign.declinedA signer declined; the request stopped and carries their reason.
webhook.testSent by the Send test button.

The same list, with example payloads, is returned by GET /api/v1/webhook-endpoints and published in the API description (/api/openapi.json, under webhooks).

Where an event came from

Payment and deal events carry a source in data:

SourceMeaning
staffSomeone on a screen in MCA Manager.
apiA call to the REST API (an API key or a connected app).
processorA payment processor's callback or status poll (ACH settled, returned).
automationA rule or scheduled job (auto-default, schedule escalation, close-out).
portalA merchant, ISO or syndicator portal.
intakeEmail intake or the hosted application form.
payoffAn early payoff or renewal payoff.
importSettings → Import. Imported rows send events only when they are recent (the last 7 days) and the import's Send webhooks box was left on.

Envelope

{
  "id": "evt_01JAX8Q4T6V2",
  "event": "payment.paid",
  "version": 1,
  "occurredAt": "2026-09-22T14:03:11.000Z",
  "data": {
    "paymentId": 9001,
    "dealId": 1042,
    "amount": "425.00",
    "source": "processor"
  }
}

id is the event id. It is the same for every endpoint the event goes to and for every redelivery of it, so it is what to de-duplicate on: keep the ids you have processed and skip a repeat. version is bumped only for breaking changes to the envelope or a payload.

Headers repeat it: X-Webhook-Event-Id (the event id above), X-Webhook-Event (the event name) and X-Webhook-Id (this one delivery attempt's row, whd_…, handy when asking support about a specific delivery). Deliveries are never redirected: a 3xx from your receiver counts as a failure.

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.

Webhooks - Help