Webhooks in Apache Fineract: why you shouldn't use them, and how to do real-time events properly

6/15/2026

Webhooks in Apache Fineract: why you shouldn't use them, and how to do real-time events properly

Sooner or later, every Fineract integration hits the same wall. You need to know the moment a loan is disbursed or a repayment posts, so you can fire a notification, update a CRM, or sync your ledger. You go looking for webhooks, the way you would in Stripe or GitHub, and what you find is confusing. There is something called a hook. It looks like a webhook. You wire it up, point it at your service, and then in production you notice that some events simply never arrive.

This is one of the most common questions we get, and the honest answer has two parts. Yes, Fineract has a webhook feature. No, you should not use it for anything you care about. The real way to do real-time events is a different mechanism that Fineract already ships, and once you see it, you will not want the webhooks back. We run managed Fineract, so we have wired up a lot of these. Here is the whole picture.

The webhook feature that will quietly lose your events

Fineract inherited a hooks feature from the old Mifos X days. You register a hook against an entity and an action, say a loan being disbursed, give it a URL, and when that action happens Fineract sends an HTTP POST to your endpoint. On paper that is a webhook.

The problem is what happens when the POST does not succeed. It is fire-and-forget. There is no retry, no queue, and no record that the event ever happened. If your receiver is down for a deploy, or the request times out, or the Fineract process restarts at the wrong moment, the event is gone. Not delayed, not retried later, gone. The dispatch code catches the failure and writes it to a log, and that is the end of it. Nobody downstream ever finds out the loan was disbursed.

There are smaller annoyances on top of that. The payload is thin, so a common pattern is that the hook tells you something happened and you then have to call the API back to find out what, which defeats much of the point. The feature is barely documented; the project's own reference for it is essentially a stub. And people hit rough edges constantly: a recurring kind of question on the mailing lists is someone registering a web hook, getting a bare 500 back, and not knowing why.

None of this means the feature is broken, exactly. It is fine for a demo, or a low-stakes internal nudge where losing the occasional event costs you nothing. But Fineract runs a ledger. For anything that touches money, a mechanism that silently drops events is not a mechanism you can build on. That is why our own documentation tells you, flatly, to treat Fineract as having no webhook you would want to use in production.

Why a webhook is the wrong shape for a ledger anyway

Step back from the specific implementation, because the deeper problem is structural, and it would bite any naive webhook.

When a loan is disbursed, two things need to be true together: the disbursement is committed to the database, and the outside world is told about it. A plain HTTP push cannot guarantee that, because the push lives outside the database transaction. The loan commits, then Fineract tries to POST, and if that POST fails you are left in a split-brain state: the money moved, your downstream systems never heard, and there is nothing on disk to reconcile against later. For a notification that might be tolerable. For your accounting sync or your card processor, it is a reconciliation nightmare waiting to happen.

What you actually want is for the event to be committed in the same breath as the business change, so that either both happen or neither does, and then for delivery to be something that retries until it succeeds rather than something that gets one shot. That pattern has a name, the transactional outbox, and it is exactly what Fineract's other event mechanism implements.

Two flows for the same event, a loan being disbursed. The top flow, labelled the webhook and fire-and-forget, goes from loan disbursed to an HTTP POST to your URL to your service, with a red cross on the delivery arrow marking the event as lost: receiver down, a timeout, or a restart, and the event is gone with no retry and no record. The bottom flow, labelled the reliable way and a transactional outbox to a broker, goes from loan disbursed to an outbox row written in the same transaction, to a drain job that runs every minute, to Kafka or ActiveMQ, to your consumers: written with the transaction, retried until the broker acknowledges it, ordered, and replayable.
Same event, two fates. A fire-and-forget POST is lost when delivery fails; the outbox commits the event with the transaction and retries until the broker has it.

The right answer: the external events framework

Alongside the old hooks, Fineract has a newer and much better system called external events, sometimes called business events. It is the one to use, and it works like this.

When a domain action completes, Fineract writes a row describing the event into an outbox table, m_external_event, inside the same database transaction as the action itself. So the disbursement and its event record commit atomically: if one rolls back, so does the other. There is no window where the loan moved but the event was lost.

A scheduled job then drains that table. It picks up the unsent rows, publishes them to a message broker, and only marks them as sent once the broker has acknowledged them. If the broker is down, the rows simply stay in the table and go out on the next run, so a broker outage delays your events but never loses them. The broker is either Kafka or ActiveMQ over JMS, and from there any number of consumers can subscribe independently: one for notifications, one for your CRM, one for accounting, none of them coupled to Fineract or to each other.

The delivery semantics are honest about the tradeoffs. Events arrive at-least-once, which means a consumer can occasionally see the same event twice, so each event carries an idempotency key and your consumer is expected to deduplicate on it. Ordering is preserved per entity, so all the events for a given loan arrive in the order they happened. The payloads are typed with versioned schemas rather than ad-hoc JSON, so a consumer cannot silently misread a changed field. There are dozens of event types covering the loan lifecycle, transactions, charges, savings, clients, and more, and they are individually switchable through a configuration API without a restart. You turn the whole thing on with FINERACT_EXTERNAL_EVENTS_ENABLED and point it at your broker.

Two honest caveats, because they matter for design. First, this is not sub-second. The job that drains the outbox runs once a minute, so worst-case latency from action to broker is on the order of a minute, which is fine for notifications and ledger sync and wrong for anything that needs to react in real time within the same second. Second, it is at-least-once and not exactly-once, so idempotent consumers are not optional. Get those two things right and you have an event pipeline you can actually trust with money.

So which one do you use?

Most of the time, the decision comes down to three options, and it is worth being deliberate about it.

Polling the API is the no-infrastructure option. You call Fineract on a schedule with date filters and pull what changed since last time. It is genuinely the right answer for batchy, low-stakes synchronization, a nightly export to a data warehouse, an hourly CRM refresh. Its weaknesses are latency and that it can miss a state that flips and flips back between two polls, and at high frequency it puts steady load on Fineract for queries that mostly return nothing. Do not reach for it when seconds matter.

The webhook feature is for demos and signals you can afford to lose. If you want a Slack ping when a client is created on your test environment, fine. Do not put anything you have to reconcile behind it.

The external events framework to a broker is the production answer for anything you cannot lose: customer notifications on disbursement and repayment, real-time CRM updates, collection triggers on overdue events, accounting and card-processor sync, fraud monitoring. It costs you a broker to run and idempotent consumers to write, and in exchange you get durability, ordering, replay, and clean decoupling.

There is one more case worth naming. If a system you are integrating genuinely speaks HTTP webhooks and nothing else, a Zapier trigger, a Slack incoming webhook, some third-party endpoint, you do not go back to the lossy hooks feature. You stand up a thin proxy that subscribes to the broker and turns each event into an outbound POST. It is a small service you own, with the durability sitting safely on the broker side of it, and it gives the downstream the webhook shape it wants without inheriting the fire-and-forget problem.

So, what should you do?

If you are doing a daily sync and latency does not matter, poll the API and move on; you do not need any of this. The moment you need to act on events as they happen, and especially the moment money or your ledger is involved, use the external events framework with a real broker, make your consumers idempotent, and leave the old webhook feature for demos. Our webhooks and integration guide lays out the patterns, and the business events guide covers the setup in detail.

The catch, and you can probably see it coming, is that the production answer asks you to run and operate a message broker, keep the outbox draining, and build idempotent consumers, which is real work on top of everything else self-hosting already asks. We costed that operational load out in a separate post.

That is the part we take off your plate. Finecko runs managed Apache Fineract with the events pipeline and a broker already wired up, so you subscribe to a clean, durable, ordered stream of events instead of standing up the plumbing yourself. If you would rather run it yourself, the rule of thumb is simple: poll for the things you can afford to be late on, use the outbox-to-broker framework for the things you cannot afford to lose, and never trust the old webhooks with either.

Skip the ops. Run managed Apache Fineract.

Finecko runs managed Apache Fineract for you - the Finecko Hub, the right topology, connection pooling, backups, TLS, patching, and on-call. You get the open-source core without the operations, and the free plan is a full environment to try with no credit card.