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 apiKey header on a Palmetto Finance webhook?
  • Does Palmetto Finance sign webhook payloads? (No: shared API key header)
  • How do I handle accountCreated, contractSigned or milestoneAchieved events?
  • 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 it api_key, so the header name is configurable (PALMETTO_FINANCE_WEBHOOK_HEADER). Check a real delivery.
  • Compare the whole header value, case-insensitive on the name only. Do not strip or parse a Bearer prefix. This is what Hookdeck's Palmetto Finance source does.
  • Check the credential first, for every HTTP method, then handle an empty body.
  • Return 401 on a missing or wrong key, 400 for a non-JSON body, 200 once 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 (clientId and clientSecret) 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.

EventSent When
accountCreatedA new account is created
accountUpdatedAn account is updated
sendInviteA consumer application invite is sent
applicationStatusA credit application's status changes
contractApprovedA signed contract is approved by Palmetto Finance
contractSendingAn async contract send has begun
contractSentA contract is sent to the consumer
contractSignedThe consumer signs a contract
contractVoidedA contract is voided
contractReinstatedA voided contract is reinstated
documentStatusAn uploaded document's status changes
documentUploadedA new document is uploaded
milestonePackageMilestone package events
milestoneAchievedA milestone is reached
milestoneStatusChangedA milestone's status changes
quoteCreatedA quote is created
quoteVoidedA quote is voided
requirementStatusChangedAn account requirement's status changes
requirementCompletedAn account requirement is completed
termsAndConditionsAcceptedTerms and conditions are accepted
allConsumerTaskEventsA 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 11, 2026

View on GitHub →