Zopa API Integration Guide
This guide will help your developers integrate the Zopa API (e-commerce widget, calculator widget, payments widget).
Hover over the keys section to generate your public and secret keys, if you haven't done so already.
Your secret key must remain secret at all times—never shared with anyone and never used in any frontend code. It is meant to be used only in server-side code to generate an HMAC signature.
Invalid signature: If Zopa returns 400 "Invalid signature", check: (1) Secret key has no leading/trailing spaces and is for the correct environment (sandbox vs live). (2) The exact same JSON string is used for the request body and for the HMAC input (base64 then HMAC-SHA256 with the secret key).
API Keys
| Key | Description |
|---|---|
| Your Public Key | The public key you send with every HTTP request. |
| Your Secret Key | Used to generate the HMAC signature. Never share it and never use it in frontend code. |
| Your Domain | The domain on which you intend to use the Zopa e-commerce solution. Required for the integration. |
| Webhook URL | Optional. URL where we post status updates for your loan applications. |
| Payment Callback Url | Callback URL after the customer completes payment on their bank side. Configure this in the Zopa portal. |
Client payload vs Zopa API payload
What you send to FinMatch finance-assistant is not the same as what we send to Zopa:
- Client → finance-assistant:
merchantID,environment,orderId,amount,verticalId,apr,term,deposit,deferredPeriod,productType,primaryApplicant,notification. (NopublicKey– we look up the merchant and add it server-side.) - Finance-assistant → Zopa:
publicKey(from merchant config),orderId,amount,verticalId,apr,term,deposit,deferredPeriod,productType,primaryApplicant,notification. (Same field names as the Zopa doc; we addpublicKeyand sign with the merchant secret.)
So your client payload is correct if it includes merchantID, environment, and the quote fields; we build the Zopa payload and HMAC on the server.
Payment Callback Url (Zopa portal)
Configure Payment Callback Url in the Zopa portal so that after the customer finishes payment on the bank side, Zopa redirects them to your site. This is separate from the optional Webhook URL (which is for server-to-server status updates).
What to use:
- A URL on your domain that shows an order/application confirmation (e.g. thank-you page).
- Example:
https://www.finmatch.io/order-confirmationorhttps://www.finmatch.io/zopa/callback. - Zopa may append query parameters (e.g.
orderId,applicationId,status); your page can read these and show the right order/status.
Where to set it: In the Zopa portal, in the same keys/config section as Your Domain and Webhook URL — paste the full URL (with https://) into the Payment Callback Url field and save.
HMAC Generation Guide
Step 1: Generate the HMAC signature
Create the base64 representation of your JSON object and generate the HMAC signature using the base64 string and your secret key.
Parameters:
data(Required) — Type:json— The JSON object of the loan product and customer data.secret_key(Required) — Type:string— Your secret key generated above.
Example (PHP):
public static function getVendigoHeaderHash($data, $secret_key)
{
$base64 = base64_encode($data);
return hash_hmac('sha256', $base64, $secret_key);
}
Step 2: Create a signature header string
Add a new header to your request:
- Signature (Required) — Type:
string— Your HMAC signature from Step 1.
Create a Quote
Overview
Using this API endpoint you can create a new Zopa application.
Request
This endpoint accepts only POST requests.
POST https://app.zopa.demo.vendigo.com/v1/common/quote
Payload
Request body (JSON):
{
"publicKey": "Sqta2fk6i7pUNd1HhrgmOFdh4ynPGpVD",
"orderId": "ab2c",
"amount": 1250,
"verticalId": 19,
"apr": 0,
"term": 12,
"deposit": 375,
"deferredPeriod": 0,
"productType": "IFC",
"primaryApplicant": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"phone": "+442039650996"
},
"notification": {
"email": true,
"sms": true,
"message": "maximum 500 chars"
}
}
Fields:
| Field | Required | Type | Description |
|---|---|---|---|
publicKey | Yes | string | Your public key (generate at the top of this page). |
orderId | Yes | string | A unique order ID or reference from your system. |
amount | Yes | float | The purchase amount. Deposit is deducted from this to create the loan amount. |
verticalId | Yes | integer | The ID of the vertical. |
apr | Yes | float | The product APR. |
term | Yes | integer | The product term. |
deposit | Yes | float | The deposit amount. Cannot exceed 50% of the purchase amount. |
deferredPeriod | Yes | integer | The deferred period of the loan product. |
productType | Yes | string | The product type. |
primaryApplicant | Yes | object | Primary applicant information. |
primaryApplicant.firstName | Yes | string | Primary applicant first name. |
primaryApplicant.lastName | Yes | string | Primary applicant last name. |
primaryApplicant.email | Yes | string | Primary applicant email. |
primaryApplicant.phone | Yes | string | Primary applicant phone number. |
notification | Yes | object | Notification settings. |
notification.email | Yes | boolean | Set to true to receive email updates from Zopa. |
notification.sms | Yes | boolean | Set to true to receive SMS updates from Zopa. |
notification.message | No | string | Optional message when creating a quote. |
Header
The HMAC hash must be calculated from your payload and sent in the Signature header.
Signature: 453d64fcc70648f6671d9c21c4365be60ee4120721531f81a267315d19283db8
- Signature (Required) — Type:
string— HMAC signature generated from your payload.
Response
Example response:
{
"applicationId": 8958,
"orderId": "ab2c",
"status": "QUOTE",
"eSignUrl": null,
"refNumber": "96VI6S9U",
"refNumberLender": "81283951",
"referredReason": [],
"satisfactionNoteUrl": null,
"applicationUrl": "https://app.zopa.demo.vendigo.com/applications/continue-with-customer/beba37f52f13f5dada997ac5b7c811ff"
}
| Field | Description |
|---|---|
applicationId | Zopa's loan application ID. |
orderId | Your reference number from your system. |
status | Loan application status. |
eSignUrl | E-sign URL for the customer to sign loan contracts (Zopa or third-party). |
refNumber | Zopa's reference number. |
refNumberLender | Lender's reference number (often empty at this step). |
referredReason | If the customer is referred, explains why and what they need to do; underwriter review may take some time. |
satisfactionNoteUrl | URL to the electronic SAT note the customer must sign if satisfied with the job. |
applicationUrl | Zopa application URL. |
Verification and testing
How to verify
- Success: Zopa returns 200 with a JSON body containing
applicationId,orderId,status(e.g."QUOTE"),refNumber, andapplicationUrl. Your integration is correct when you get this. - Invalid signature: Zopa returns 400 with a message like
"Invalid signature". Fix: use the exact same JSON string for the request body and for the HMAC input, and ensure the secret key is correct (no spaces, correct environment). - Other 4xx/5xx: Check the response body for Zopa’s error message (e.g. validation, invalid key, rate limit).
Sandbox
- Use the sandbox (demo) environment for testing: base URL
https://app.zopa.demo.vendigo.com, and create quote withPOST /v1/common/quote. - Use the public and secret keys from the Zopa portal for the sandbox environment (hover to reveal/generate).
Testing with curl
- Build the JSON payload (same key order as in this guide).
- Compute the signature: base64(JSON string) → HMAC-SHA256 with your secret key → hex.
- Send the request with the Signature header set to that hex value.
You can use the script below to generate the Signature header for a given payload, then call Zopa with curl:
# From finmatch-shared/cloud-run/finance-assistant:
node scripts/zopa-sign-payload.js '<json_payload>' '<secret_key>'
# Then use the printed Signature in curl:
curl -X POST https://app.zopa.demo.vendigo.com/v1/common/quote \
-H "Content-Type: application/json" -H "Signature: <hex_from_script>" \
-d '<json_payload>'
The payload must be the exact same string in both the script and -d (no extra spaces or key reordering).
Mark Job as Completed
Overview
This endpoint notifies customers that the job is completed and the "Satisfaction note" document can be signed.
Request
This endpoint accepts only POST requests.
POST https://app.zopa.demo.vendigo.com/v1/common/job-completed
Payload
{
"publicKey": "Sqta2fk6i7pUNd1HhrgmOFdh4ynPGpVD",
"refNumber": "96VI6S9U",
"tmLodgementNumber": 5001
}
| Field | Description |
|---|---|
publicKey | Your public key (generate at the top of this page). |
refNumber | Zopa's reference number for this loan application. |
applicationId | Zopa's loan application ID. |
tmLodgementNumber | The Trustmark Lodgment number if the merchant has it. |
Header
Include the HMAC signature as the Signature header (same process as above).
Signature: d7f6bc8633bca4103bac42abc60d59d3bcd7c721aad82577978cf042ba1f440f
Response
{
"applicationId": 8859,
"orderId": "ab2c",
"status": "QUOTE",
"eSignUrl": "https://puat-esign.bnpparibas-pf.co.uk/partner.aspx?PartnerRedirectId=59887707-4f8d-4435-9662-d641a1ea6246",
"refNumber": "M80DYQH8",
"refNumberLender": "81283951",
"referredReason": [],
"satisfactionNoteUrl": "https://app.pactsafe.com/sign?r=612de9744b57d309a9f04c47&s=5cb877478d04324965ff80c7&signature=...",
"applicationUrl": "https://app.zopa.demo.vendigo.com/applications/detail/357decab40567605505844d245d81c63"
}
| Field | Description |
|---|---|
applicationId | Zopa's loan application ID. |
orderId | Your reference number. |
status | Loan application status. |
eSignUrl | E-sign URL for the customer to sign loan contracts. |
refNumber | Zopa's reference number. |
refNumberLender | Lender's reference number. |
referredReason | Explains referral and next steps for the customer. |
satisfactionNoteUrl | URL to the electronic SAT note. |
applicationUrl | Zopa application URL. |
Get All Meta Data
Overview
Returns unique identifiers, constants, and enums used to manage Zopa applications.
Request
This endpoint accepts only GET requests.
GET https://app.zopa.demo.vendigo.com/v1/common/meta?publicKey=Sqta2fk6i7pUNd1HhrgmOFdh4ynPGpVD
publicKey— Your public key (generate at the top of this page).
Response
Response is unique per merchant and returns only valid data for the current merchant.
{
"currency": "GBP",
"language": "en-gb",
"deposit": {
"minPercent": 0,
"maxPercent": 50
},
"termsAndConditions": "https://www.vendigo.com/docs/customer-terms/",
"privacyPolicy": "https://www.vendigo.com/docs/privacy-policy/",
"vertical": [
{ "id": 19, "name": "Renewables" }
],
"product": [
{ "apr": 0, "term": 12, "deferredPeriod": 0, "type": "IFC" },
{ "apr": 0, "term": 24, "deferredPeriod": 0, "type": "IFC" },
{ "apr": 0, "term": 36, "deferredPeriod": 0, "type": "IFC" },
{ "apr": 0, "term": 48, "deferredPeriod": 0, "type": "IFC" },
{ "apr": 0, "term": 60, "deferredPeriod": 0, "type": "IFC" },
{ "apr": 11.9, "term": 24, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 11.9, "term": 36, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 11.9, "term": 48, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 11.9, "term": 60, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 11.9, "term": 84, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 11.9, "term": 120, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 10.9, "term": 24, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 10.9, "term": 36, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 10.9, "term": 48, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 10.9, "term": 60, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 10.9, "term": 84, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 10.9, "term": 120, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 13.9, "term": 24, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 13.9, "term": 36, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 13.9, "term": 48, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 13.9, "term": 60, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 13.9, "term": 84, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 13.9, "term": 120, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 13.9, "term": 96, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 0, "term": 18, "deferredPeriod": 0, "type": "IFC" },
{ "apr": 9.9, "term": 18, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 9.9, "term": 24, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 9.9, "term": 36, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 9.9, "term": 48, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 9.9, "term": 60, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 9.9, "term": 84, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 9.9, "term": 96, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 9.9, "term": 120, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 5.9, "term": 18, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 5.9, "term": 24, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 5.9, "term": 36, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 5.9, "term": 48, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 5.9, "term": 60, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 5.9, "term": 84, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 5.9, "term": 120, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 10.9, "term": 18, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 10.9, "term": 96, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 11.9, "term": 18, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 11.9, "term": 96, "deferredPeriod": 0, "type": "IBC" },
{ "apr": 13.9, "term": 18, "deferredPeriod": 0, "type": "IBC" }
]
}
| Field | Description |
|---|---|
currency | Current currency in the Zopa system. |
language | Current language (e.g. en-gb). |
deposit.minPercent | Minimum deposit percentage for an application. |
deposit.maxPercent | Maximum deposit percentage for an application. |
termsAndConditions | URL to terms and conditions. |
privacyPolicy | URL to privacy policy. |
vertical | List of verticals (each with id, name). |
product | List of products (each with apr, term, deferredPeriod, type). |
Application Status Updates
You can call the /status endpoint to get application updates. The HMAC signature must be sent in the HTTP header. Send your public key and the application ID in the JSON payload.
Example (cURL):
curl --request POST \
--url https://app.zopa.demo.vendigo.com/v1/loan-application/status \
--header 'Content-Type: application/json' \
--header 'Signature: The HMAC signature goes here' \
--header 'cache-control: no-cache' \
--data '{"publicKey":"Sqta2fk6i7pUNd1HhrgmOFdh4ynPGpVD","applicationId":1}'
Updates can also be pushed to the webhook URL you configured. In both cases the response has the following structure:
Response:
{
"applicationId": 1,
"orderId": "1",
"status": "APPROVED",
"eSignUrl": "https://trial.bonafidee.com/signsolo.aspx?t=F4E8D696-AD32-4E3D-B59D-7D6812C20B91",
"refNumber": "S16KYWRS",
"refNumberLender": "GNPDNCOK",
"referredReason": [],
"satisfactionNoteUrl": "https://app.pactsafe.com/sign?r=5cd2ee760ba7427150e9e4sdf8",
"applicationUrl": "https://app.zopa.demo.vendigo.com/applications/detail/357decab40567605505844d245d81c63",
"product": {
"productType": "IBC",
"apr": 9.90,
"term": 60,
"deferredPeriod": 0
}
}
| Field | Description |
|---|---|
applicationId | Zopa's loan application ID. |
orderId | Your internal order ID or reference. |
status | Loan application status. |
eSignUrl | E-sign URL for the customer (Zopa or third-party). |
refNumber | Zopa's reference number. |
refNumberLender | Lender's reference number. |
referredReason | Referral reason and next steps; may take time to update after underwriter review. |
satisfactionNoteUrl | URL to the electronic SAT note. |
applicationUrl | Zopa application URL. |
product | The product associated with the loan (productType, apr, term, deferredPeriod). |