Webhooks
Money moves on its own clock, so the parts of this API you care most about — the vendor being paid, the payer settling — happen long after your request returned. Subscribe once and stop polling.
Code
Leave eventTypes out to get everything, including events we add later — which is what most
integrations want. The response carries the signing secret, once. We never return it again.
The endpoint must be https on the public internet. We refuse anything resolving to a private,
loopback or link-local address, and we do not follow redirects.
Events
| Event | When |
|---|---|
partner.organization.added | a business you synced exists at Lunch |
partner.organization.optedIn | it has cleared onboarding |
partner.organization.remittanceUpdated | where its money goes has changed |
partner.invoice.created | an invoice you synced exists |
partner.invoice.paid | the payer settled it |
partner.invoice.factoredUpdated | how much of it is financed has changed |
partner.loan.created | an advance was taken |
partner.loan.issued | the vendor has been paid |
partner.loan.paid | the advance is settled |
Verifying a delivery
Every delivery carries Lunch-Signature: t=<unix seconds>,v1=<hex>. The signature is HMAC-SHA256
over the exact string "<timestamp>.<raw body>", keyed with your subscription secret.
Code
Check the timestamp, not just the signature
Without the age check the signature says only that the body was ours once — not that this delivery is fresh. Reject anything older than five minutes.
Three properties a receiver has to respect
At-least-once. A retry can arrive after a success you failed to record. Lunch-Delivery is
stable across attempts — key your deduplication on it.
Unordered, and it does not resolve itself. A delivery that failed and was retried can land
after a later one succeeded, so a financing update can arrive behind the state that replaced it.
partner.invoice.factoredUpdated carries a sequence for exactly this: ignore one whose sequence
is not greater than the one you already hold for that invoice.
occurredAt is not an ordering key. It is the transaction's start time, so two events can
carry stamps in the opposite order to their causes. It is for your logs.
Answering
2xx accepts. Anything else is retried over about 28 hours. Answer 410 when the endpoint is
gone for good and we stop and revoke the subscription.
GET /v1/webhooks lists your live subscriptions, secrets omitted.
DELETE /v1/webhooks/{reference} stops one.

