Tento Partner Webhooks
Tento webhooks let your systems react in real time when data you have access to changes — instead of polling our API. When a relevant event occurs, Tento sends an HTTP POST with a JSON payload to an endpoint you control.
A webhook only ever delivers data you could already retrieve from the Partner API for that resource, and only for businesses and applications referred through your integration.
Creating a Webhook
Webhooks are managed in the Volt platform. Once your partner account is set up:
- Log in to Volt with your partner credentials
- Navigate to Webhooks
- Enter your HTTPS endpoint URL and choose your subscriptions (see below)
- Submit — new webhooks require Tento approval before any events are sent
- Copy your signing secret when it is shown — it is displayed only once and is used to verify incoming requests
The endpoint URL and subscriptions are locked after approval. To change them, create a new webhook or contact us.
Subscriptions
When you create a webhook you choose which categories of events to receive:
| Subscription | Scope value | Events it delivers |
|---|---|---|
| Business credit scores | business_credit_score | business_credit_score.updated |
| Loan applications | loan_application | loan_application.created, loan_application.stage_changed, loan_application.updated |
Event Types
Event type | Sent when |
|---|---|
business_credit_score.updated | A business credit score you have access to is refreshed with a new successful result. |
loan_application.created | A new financing application is created for a business you referred. |
loan_application.stage_changed | A financing application's stage changes. |
loan_application.updated | A financing application changes in one or more fields visible to you (other than stage). Changes to internal-only fields never trigger an event. |
Payload Envelope
Every delivery has the same top-level shape:
| Property | Type | Description |
|---|---|---|
id | string | Unique event id (prefixed evt_). Stable across retries — use it for idempotency. |
type | string | The event type (see above). |
createdAt | string (ISO‑8601) | When the event was generated. |
livemode | boolean | true in production, false in staging/test. |
data | object | The event-specific payload (see below). |
{
"id": "evt_4f3c2b1a-…",
"type": "loan_application.stage_changed",
"createdAt": "2026-06-04T14:21:09.512Z",
"livemode": true,
"data": { }
}Request Headers
Each POST includes:
| Header | Value | Purpose |
|---|---|---|
Content-Type | application/json | — |
User-Agent | Tento-Webhooks/1.0 | Identifies Tento as the sender |
Tento-Signature | t=<unix_seconds>,v1=<hex_hmac> | Verify authenticity (see below) |
Tento-Event-Id | evt_… | Mirrors id for idempotency |
Tento-Event-Type | e.g. business_credit_score.updated | Route without parsing the body |
Verifying Signatures
Each request is signed with your webhook's signing secret. The Tento-Signature header contains a timestamp t and a signature v1. To verify, compute an HMAC‑SHA256 over the string "{t}.{raw_request_body}" using your signing secret and compare it (in constant time) to v1.
Node.js
const crypto = require('crypto')
function verifyTentoSignature(rawBody, signatureHeader, signingSecret) {
const parts = Object.fromEntries(signatureHeader.split(',').map((kv) => kv.split('=')))
const expected = crypto
.createHmac('sha256', signingSecret)
.update(`${parts.t}.${rawBody}`)
.digest('hex')
return (
expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
)
}Python
import hashlib
import hmac
def verify_tento_signature(raw_body: bytes, signature_header: str, signing_secret: str) -> bool:
parts = dict(kv.split("=", 1) for kv in signature_header.split(","))
expected = hmac.new(
signing_secret.encode("utf-8"),
f"{parts['t']}.".encode("utf-8") + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Always verify against the raw request body bytes, before any JSON parsing/re-serialization. Reject requests whose timestamp is too old to mitigate replay.
data — business_credit_score.updated
data — business_credit_score.updated| Property | Type | Description | Example |
|---|---|---|---|
businessId | string | Public id of the business. | "5nv7oy1djllr" |
score | number | null | Latest business credit score, or null if unavailable. | 712 |
date | string (ISO) | null | Score validity date, or null. | "2026-06-01T00:00:00.000Z" |
{
"id": "evt_…",
"type": "business_credit_score.updated",
"createdAt": "2026-06-04T14:21:09.512Z",
"livemode": true,
"data": {
"businessId": "5nv7oy1djllr",
"score": 712,
"date": "2026-06-01T00:00:00.000Z"
}
}data — loan_application.*
data — loan_application.*| Property | Type | Description |
|---|---|---|
applicationId | string | Public id of the financing application. |
changedFields | string[] | Fields that changed on this update. Empty for created. |
application | object | The full application object (identical to GET /application). |
Possible changedFields values: stage, amount, terms, termLength, termUnit, product.
{
"id": "evt_…",
"type": "loan_application.stage_changed",
"createdAt": "2026-06-04T14:21:09.512Z",
"livemode": true,
"data": {
"applicationId": "37up94exxfz7",
"changedFields": ["stage"],
"application": { "...": "see below" }
}
}The application object
application objectTop level
| Property | Type |
|---|---|
id | string (application public id) |
userId | string |
product | string |
loanRequest | object |
business | object |
files | object |
contacts | object[] |
loanRequest
| Property | Type | Property | Type | |
|---|---|---|---|---|
id | string | termLengthAgreed | number | null | |
stage | string | termUnit | string | |
amount | number | termUnitAgreed | string | null | |
closedAmount | number | null | source | string | |
closedAt | string (ISO) | null | equipmentDesc | string | null | |
type / types | string / string[] | whenFundingNeeded | string | null | |
bankStatements | object | partner | object | |
termLength | number | account | object |
business
| Property | Type | Property | Type | |
|---|---|---|---|---|
id | string | phone | string | |
name | string | mobile | string | |
doingBusinessAs | string | email | string | |
entityType | string | website | string | |
taxIdNumber | string (EIN) | employees | number | |
naicsCode | string | home_based | boolean | |
naicsDescription | string | facebookReviewed | boolean | |
annualRevenue | number | lendingTreeReviewed | boolean | |
businessInceptionDate | date | googleReviewed | boolean | |
businessYears | number | address | { street, city, state, postalCode } |
files — booleans: hasUploadedBankStatements, hasUploadedDriverLicense, hasUploadedVoidedCheck, hasUploadedBusinessTaxReturn, hasUploadedEquipmentInvoice
contacts[] — id, isMainContact, firstName, lastName, title, email, dob, isSubmitter, ssn (masked, e.g. ***-**-1234), phoneHome, phoneMobile, ownerPercentage, address { street, suite, city, state, postalCode }
Delivery, Retries & Suspension
| Aspect | Behavior |
|---|---|
| Method | POST JSON to your endpoint |
| Expected response | 2xx within 10 seconds |
| Retries | Up to 3 attempts total (initial + 2 retries), with short backoff (~20s, ~40s) |
| Ordering | Not guaranteed — use createdAt and treat events as hints to re-fetch if needed |
| Idempotency | Retries reuse the same id / Tento-Event-Id — de-duplicate on it |
| Auto-suspend | A webhook is suspended after 25 failed deliveries in 5 days or 50 consecutive failures. Contact Tento to re-enable. |
| Forward-only | Only events generated after your webhook is approved are delivered; there is no backfill. |
Security Best Practices
- Always verify the
Tento-Signatureheader before trusting a payload - Respond
2xxquickly and process asynchronously to avoid timeouts and retries - Treat the signing secret like a password — store it securely; rotate it from Volt if exposed
- Use separate endpoints/secrets for staging and production
Staging vs. Production
| Environment | livemode | Notes |
|---|---|---|
| Staging | false | Use a staging endpoint and signing secret to test integrations safely. |
| Production | true | Live business and application data. |
Contact the Tento team to enable webhooks for your partner account and to obtain a staging setup.
