Partner webhooks
Admin → Partner webhooks
(https://admin.finmatch.io/partner-webhooks/). Requires
admin:partner-webhooks. Without that grant the left-nav item is
hidden and a direct URL is refused. James grants it by hand in
auth-credentials.json (same as admin:applications); sign out and
back in afterwards. Role admin passes the server check, but the
sidebar still needs the grant. Catalogue label: Partner webhooks.
See Users.
H1 Partner webhooks. Subtitle: Set where each partner receives credit status notifications, which merchants' applications they're told about, and the keys we sign them with.
This page configures push notifications. It is not the Partner API quote path. Visible copy says notification, not event. Do not paste signing secrets or partner API keys onto this public page.
Hint under Reload: Changes save straight away and take effect within a minute.
Banner when push is switched off on finance-assistant
(PARTNER_WEBHOOKS_ENABLED): Credit status notifications are switched off on finance-assistant (PARTNER_WEBHOOKS_ENABLED), so test notifications and the lookup are unavailable. You can still prepare settings.
Load failure: Failed to load partner webhooks: plus the error. Empty partner list: No Partner API partners found. Badge on a partner with no settings yet: Not set up. Configured and sending: Enabled. Configured and paused: Off. Unavailable tests: Not available: credit status notifications are switched off on finance-assistant. If the Partner API key list cannot be read: The Partner API key list could not be read, so only partners that already have settings are listed.
Partner picker
Partner dropdown lists Partner API partners. Reload re-reads settings and the merchant list. Pick a partner before editing the cards below.
Where notifications go
Card Where notifications go. Intro: Turn notifications on or off, and set the test and live addresses we send to. When nothing is stored yet: This partner isn't set up yet. Saving creates its settings, with notifications switched off.
- Enabled: send this partner credit status notifications for the merchants below.
- Sandbox endpoint — placeholder
https://partner.example/finmatch/sandbox - Live endpoint — placeholder
https://partner.example/finmatch - Email copies to (optional) — every notification, including tests. — placeholder
ops@partner.example. One address per line, comma, or semicolon; at most 10. Invalid save: An address under "Email copies to" is not a valid email address (or there are more than 10), so nothing was saved.
Endpoints are checked while typing (once past https://, or to clear an
error as it is fixed), on blur, and again on Save settings. Messages
appear under the field:
- Must start with https://
- Remove the query string (?…)
- This address isn't reachable from the internet (private, loopback, link-local, metadata,
.internaland.localhosts) - Enter a valid web address
- Remove the username and password from the address
- Remove the #… part from the address
The invalid field gets a red border (aria-invalid). Save settings
is disabled while either endpoint is invalid. The server stays the
source of truth: if it rejects an address, that message appears under
the named field, the typed value stays, and the status reads Not saved. Fix the address above.
Which merchants they hear about
Card Which merchants they hear about. Intro: The partner is only told about applications for merchants listed here.
Columns: Merchant, Granted, Granted by, Consent, then Remove. Empty: No merchants yet. The partner gets no notifications until you add one.
Add row: searchable Merchant combobox (listbox Merchants,
placeholder Search by name or id, e.g. M000106). Results show
Name · M000106. Linked merchants are left out. Idle chosen line:
No merchant chosen. After a pick: Merchant id: plus the id
(that id is what is submitted). Empty merchant-api list: The merchant list is empty, so there is no merchant to add.
Load failure: Couldn't load the merchant list plus the error, then
Click Reload to try again. While fetching: Loading merchants…
No matches: No merchant matches, or it is already on this partner's list.
Empty search when every merchant is already listed:
Every merchant is already on this partner's list.
Keyboard: ArrowUp / ArrowDown, Enter chooses, Escape closes, Tab moves
on.
Consent note: how the merchant agreed in writing — placeholder
Written consent by email from Jo Bloggs, 27 Sep 2026. Add merchant.
No merchant chosen: Choose a merchant from the list first. Empty
consent: Note how the merchant agreed before adding them.
The list is GET /api/merchants (same as the Merchants page). The
server still refuses an id that is not in merchants.json.
Keys we sign with
Card Keys we sign with. Intro: The partner uses these to check a notification really came from us. Each key is shown once.
Columns Sandbox and Live. Empty environment: No key. Notifications in this environment are not sent. Generate new key (disabled with title Retire a key first when two keys already exist). Retire removes an old key after the partner has moved.
Hint: Up to two keys per environment. With two, both sign every notification, so the partner can switch to the new key before you retire the old one.
Generate asks for the admin password
(Enter your admin password to generate a signing key:). The secret
appears once in an amber box: Copy this secret now. It will not be shown again.
Buttons Copy and I have stored it. Copy success: Copied.
After I have stored it and Reload, the secret is gone. Do not
paste the value into /docs/.
Rotation: generate a second key (both sign), the partner moves to it, then Retire the old one.
Send a test notification
Card Send a test notification. Intro: Pick a lender status and send a clearly marked test. Nothing real is affected.
The sample is a form, not raw JSON. Editable: Merchant, Merchant
reference, and Lender status to simulate. Lender is a
dropdown; Zopa only for now. Loan amount (£) for Zopa is not an
editable box: operators see Zopa does not send a loan amount.
(A lender that does send an amount would show the two-decimal pounds
field, default 9000.00; invalid: Enter pounds with two decimals, e.g. 9000.00; Must be more than 0.00. Fallback line if that
lender still has no amount: This lender does not send a loan amount.)
Environment comes from the button you press.
Prefill: first merchant Linked to this partner, else the first
Other merchants row (Send stays disabled until you pick a linked
one); Merchant reference QB-10452.
- Merchant — optgroups Linked to this partner then Other merchants (or Merchants when none are linked). Only linked merchants (the same grant list as Which merchants they hear about, with sharing on) can be sent. Other merchants stays in the list for reference; choosing one shows Choose a merchant linked to this partner and Send sandbox test / Send live test stay disabled until a linked merchant is picked. Default is the first linked merchant, else the first other (for example M000106 when it is not on this partner). Not in the directory: That merchant is not in the merchant list. Server 400 for an unlinked merchant uses the same field error; nothing is POSTed and no partner email copy is sent.
- Merchant reference — default
QB-10452. Invalid: Letters, digits, dots, dashes and underscores only, up to 64. - Lender — dropdown, Zopa only for now.
- Lender status to simulate — filled from the server. Options are every raw Zopa code, by code only, for example CONDITIONALLY_APPROVED. No friendly labels. Simulated status on the result shows the same code. Unknown scenario: That status is not one of the test choices. Idle fallback: Plain test (no status).
- Loan amount (£) — for Zopa, hidden input and the line Zopa does not send a loan amount. A crafted sample that still sends
loanAmountis refused: This field cannot be set.
Too long: Too long. Extra field: This field cannot be set. Server 400: Some sample values are not valid. See the fields above. plus the per-field messages. Nothing is sent or emailed while the sample is invalid.
Send sandbox test / Send live test are disabled while a sample field is invalid, while the chosen merchant is not linked to this partner, and when the partner is not configured or tests are unavailable.
Fixed in every test: event_type: credit_application.test,
event_id: generated, timestamp: when sent,
finmatch_reference: TEST-{merchant}-{partner}-R00000001 (or
TEST-NOT-A-REAL-APPLICATION with no merchant/partner),
lender_application_id: 8859 (typical historic Zopa shape; changes with
lender), FinMatch-Test: true. Lender application id is not a boxed
field. The TEST- reference can never parse as a minted
reference. The test never writes the Applications ledger.
Result fields: Environment, Simulated status, Status, HTTP status, Time taken, Notification id, Signed with, Email copy (and Error class / Reason on failure).
Email copy lines: Emailed to N addresses, marked TEST.; Email to N addresses failed. The test itself is unaffected.; No addresses under "Email copies to".; Not emailed: one of the addresses is not valid, so nobody was emailed.; Email copies are switched off on finance-assistant.; fallback Not emailed.
What we sent then shows the target URL, Request headers
(FinMatch-Signature, FinMatch-Key-Id, FinMatch-Test,
Content-Type and the rest — the signature is an HMAC, never a secret)
and Body. Note: Shown formatted. Copy gives the exact bytes that were signed.
What came back: HTTP status, time taken, and Response body
(First 1 KB only. when truncated). Empty partner reply: No response body.
Each block has Copy.
The signed body is eleven keys, in this order: event_id,
event_type, timestamp, environment, merchant_id,
merchant_reference, finmatch_reference,
lender, lender_application_id, lender_status, loan_amount.
Partners match a notification with merchant_reference and/or
finmatch_reference. Quote id (data.finmatch_quote_id) stays on the
Partner API quote response. lender_status is the lender's raw
code as a string, for example "CONDITIONALLY_APPROVED". Not
{ code, description }. Created events use null. Tests of a Zopa
status use the same string. For Zopa, loan_amount is null (the
test of a Zopa status does not invent an amount). Tests mark
event_type credit_application.test. The sample POST carries merchant
and merchant reference; lender application id and lender are
set server-side. Zopa omits loanAmount from the sample.
Addresses under Email copies to (including tests) get a copy of that envelope, not a second notification.
Subject (tests): [TEST] Credit Status Notification: {merchant_reference} | Status: {lender_status}
with the raw lender code, for example CONDITIONALLY_APPROVED (not
“Conditionally Approved”). Live copies drop the [TEST] prefix. If
merchant_reference is empty, the subject uses finmatch_reference. It
does not invent QB-10452. The admin test form still prefills
Merchant reference QB-10452.
Body is one labelled row per envelope key, in that same eleven-key order: Event ID, Event type, Timestamp, Environment, Merchant ID, Merchant reference, FinMatch reference, Lender, Lender application ID, Lender status code, Loan amount.
- Event type is the JSON string (
credit_application.test/credit_application.status_changed). - Merchant ID is the id only. No company name in parentheses.
- Lender status code is the raw code (for example CONDITIONALLY_APPROVED). There is no description row.
- For Zopa, JSON
loan_amountstaysnull. The email row is Loan amount: Null (Zopa does not send a loan amount) — not a dash. The test-form note is still Zopa does not send a loan amount (no “Null”).
There is no Delivery result row and no footer that mentions Also email each notification to.
Partner webhook deliveries
Card Partner webhook deliveries. Intro: Look up signed partner webhook deliveries for one FinMatch reference. This is not CSN email and not the Applications ledger. Only partner-attributed references (not P000000) get a delivery row.
This card is the staff view of partner_webhook_deliveries. It is a
partner feature surfaced in admin, not an admin-wide log of every
FinMatch reference. CSN emails and on-demand test notifications do not
appear here.
Reference placeholder e.g. M000106-P000001-R00004012, then
Look up.
Columns: Type, Partner, Env, Status, Attempts, Last HTTP, Last error, Last attempt.
Empty states:
- Lookup failed. We could not read partner webhook deliveries for this reference.
- This application never notified a partner. P000000 means no partner (FinMatch-direct), so no partner webhook delivery is created. CSN emails and the Applications ledger are separate.
- This application never notified a partner. P000000 means no partner (FinMatch-direct), so no partner webhook delivery is created. We also have no Applications ledger row for this reference — check the typing. CSN emails are separate.
- No partner webhook deliveries for this reference. It does not look like a minted FinMatch reference, so a partner webhook would not have been planned.
- No partner webhook delivery row for this reference. A skipped send (partner off, merchant not granted, no key) would show here as skipped. Empty means none was planned: webhooks off at the time, a history replay, or a same-status redelivery. This is not CSN email.
- No partner webhook delivery row, and no Applications ledger row for this reference. If a partner was notified, we cannot find it here. Check the reference, or use Applications for the ledger and CSN email.
Replay of dead deliveries is not on this page.