Agent skill
Palmetto Finance Webhooks Skill
Receive and authenticate Palmetto Finance (LightReach solar financing) webhooks. Use when setting up a Palmetto Finance webhook endpoint, checking the server API key header (apiKey), debugging 401s, or handling events like accountCreated, applicationStatus, contractSigned, milestoneAchieved, quoteCreated and documentUploaded.
Install this skill
npx skills add hookdeck/webhook-skills --skill palmetto-finance-webhooks
Palmetto Finance (LightReach solar financing) sends a webhook to your server when something changes on an account: an application status moves, a contract is signed, a milestone is achieved, a quote is created, a document is uploaded. Webhooks are not signed. With the default server API key authentication, every request carries the API key Palmetto returned when you registered the webhook in a header, and your endpoint authenticates the request by comparing that header with the key you stored.
When to Use This Skill
- How do I receive Palmetto Finance webhooks?
- How do I verify the
apiKeyheader on a Palmetto Finance webhook? - Does Palmetto Finance sign webhook payloads? (No: shared API key header)
- How do I handle
accountCreated,contractSignedormilestoneAchievedevents? - Why does my Palmetto Finance webhook endpoint return 401, and why does Palmetto not retry it?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyPalmettoFinanceRequest(
headers, // Node lowercases names
secret = process.env.PALMETTO_FINANCE_WEBHOOK_SECRET,
headerName = process.env.PALMETTO_FINANCE_WEBHOOK_HEADER || 'apiKey'
) {
if (!secret) return false; // never accept when unconfigured
const received = headers[headerName.toLowerCase()];
return Boolean(received) && safeEqual(received, secret); // WHOLE value, no 'Bearer ' parsing
}
- Header:
apiKey: <server API key>by default. Some Palmetto pages call itapi_key, so the header name is configurable (PALMETTO_). Check a real delivery.FINANCE_ WEBHOOK_ HEADER - Compare the whole header value, case-insensitive on the name only. Do not strip or parse a
Bearerprefix. This is what Hookdeck's Palmetto Finance source does. - Check the credential first, for every HTTP method, then handle an empty body.
- Return
401on a missing or wrong key,400for a non-JSON body,200once handled. - Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it. - A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the key if it leaks.
- Only the server API key mechanism is covered. Palmetto's client headers (
clientIdandclientSecret) and client basic authorization options are not covered by Hookdeck's Palmetto Finance source, and the examples don't check them.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
HTTP Methods
Each webhook subscription sets its own httpMethod (the docs example uses POST), so the examples accept GET, POST, PUT, PATCH and DELETE on the route and handle an empty body. Hookdeck's Palmetto Finance source pre-selects POST without managing the method, so any other method your subscription uses must also be allowed on the source.
Event Types
The body is a flat JSON object: an event field plus the event's own fields (accountId, applicationId, ...). Dispatch on event.
| Event | Sent When |
|---|---|
accountCreated | A new account is created |
accountUpdated | An account is updated |
sendInvite | A consumer application invite is sent |
applicationStatus | A credit application's status changes |
contractApproved | A signed contract is approved by Palmetto Finance |
contractSending | An async contract send has begun |
contractSent | A contract is sent to the consumer |
contractSigned | The consumer signs a contract |
contractVoided | A contract is voided |
contractReinstated | A voided contract is reinstated |
documentStatus | An uploaded document's status changes |
documentUploaded | A new document is uploaded |
milestonePackage | Milestone package events |
milestoneAchieved | A milestone is reached |
milestoneStatusChanged | A milestone's status changes |
quoteCreated | A quote is created |
quoteVoided | A quote is voided |
requirementStatusChanged | An account requirement's status changes |
requirementCompleted | An account requirement is completed |
termsAndConditionsAccepted | Terms and conditions are accepted |
allConsumerTaskEvents | A consumer task is created, updated or outstanding (status, activityType) |
Palmetto's docs list more subscription names (for example stipulationAdded, stipulationCleared and allStipulationsCleared); see references/overview.md.
Retries
Palmetto waits up to 30 seconds per attempt. It retries 5xx, 429 and network errors up to 9 times (10 attempts) with exponential backoff from 3 minutes. Other 4xx responses, including 401, are not retried. 10 failures disable the subscription.
Environment Variables
PALMETTO_FINANCE_WEBHOOK_HEADER=apiKey # or api_key, whatever your requests show
PALMETTO_FINANCE_WEBHOOK_SECRET=your-server-api-key # exact, whole header value
Setup Note
Use the default server API key authentication. Palmetto shows the key only once, when you register the webhook. Enter apiKey as the header unless your requests show api_key.
In Hookdeck, create a source with type Palmetto Finance, enter that header name and the key, and leave the method as the pre-selected POST unless your subscription uses another method.
Local Development
Palmetto Finance can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 palmetto-finance --path /webhooks/palmetto-finance
Use the printed URL as the registration hostUrl (and the subscription endpointUrl for the path).
Resources
- references/overview.md - What Palmetto Finance webhooks are, events and payload shape
- references/setup.md - Register a webhook, subscribe to events, store the key
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests