Skip to main content

Webhooks

Before posts a signed JSON event to your endpoint when a sale you opened reaches a milestone. Webhooks fire only for sales opened through POST /v1/partner/sessions.

Add an endpoint​

Add endpoints in the Before Partner Portal: an HTTPS URL on the public internet, the events to send, and the funeral home it serves. Like keys, endpoints belong to one funeral home. Before refuses a URL that is plain HTTP or resolves to a private, loopback or link-local address, and does not follow redirects.

The endpoint's signing secret, whsec_…, is shown once, when the endpoint is created. Rotating it in the portal shows a new secret and the old one stops signing at once.

An endpoint receives events from live and test sales alike; data.mode tells them apart.

Events​

TypeWhen
sale.signedEvery signer has signed.
sale.submittedBefore sent the application to the carrier.
policy.issuedThe carrier issued the policy. data.policy is set.
sale.declinedThe carrier declined the application.

A sale raises them in this order, ending in either policy.issued or sale.declined.

{
"id": "0d3e5c3a-8f5e-4c43-9f0e-7d1b8f9a6b21",
"type": "policy.issued",
"created_at": "2026-11-02T15:31:07.000Z",
"data": {
"sale_id": "6f1c1f0e-2b7a-4d39-9d0f-2a4f0b1d7c55",
"partner_reference": "case-1042",
"status": "issued",
"mode": "live",
"policy": { "number": "FPA-0012345", "carrier": "Forethought Life", "premium": "84.12" }
}
}
FieldMeaning
idUnique per event and endpoint. Deduplicate on it.
typeOne of the types above.
created_atWhen Before recorded the milestone.
data.statusThe sale's status when the event was sent, which can be later than the milestone.
data.policynull until the policy is issued. premium is an exact decimal string; never parse it as a float.

data is built when Before sends the event, not when the milestone happened. A retried sale.signed can arrive with status: "submitted". Act on type, and read GET /v1/partner/sales/:id when you need the current state.

Each request also carries these headers:

HeaderValue
Before-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>
Before-Event-IdThe event's id.
Before-Event-TypeThe event's type.
User-AgentBefore-Webhooks/1

Verify the signature​

Compute an HMAC-SHA256 over <t>.<raw body>, keyed with the whole whsec_… secret, and compare it in constant time with v1. Use the raw request body exactly as received: parsing and re-serialising the JSON changes the bytes. Refuse a timestamp more than five minutes from now, so a captured request cannot be replayed.

verify.js
const crypto = require('node:crypto');

function verifyBeforeSignature(secret, header, rawBody, toleranceSeconds = 300) {
const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
if (!match) return false;
const timestamp = Number(match[1]);
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest();
return crypto.timingSafeEqual(expected, Buffer.from(match[2], 'hex'));
}

With Express, read the body raw on this route only:

app.post('/webhooks/before', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8');
if (!verifyBeforeSignature(process.env.BEFORE_WEBHOOK_SECRET, req.get('Before-Signature'), rawBody)) {
return res.sendStatus(400);
}
const event = JSON.parse(rawBody);
res.sendStatus(200);
queueForProcessing(event);
});

Respond quickly​

Answer with any 2xx status within 10 seconds, then do the work. Before treats every other answer as a failure: 3xx redirects are not followed, and 4xx and 5xx statuses, timeouts and refused connections are all retried.

Retries​

Each event is one delivery per endpoint, and every delivery ends delivered or failed.

Before sends each event within about a minute of the milestone. After a failed attempt it waits min(2^attempts, 60) minutes before the next: 2, 4, 8, 16, 32, 60 and 60 minutes. After 8 failed attempts, about three hours, the event is marked failed.

  • Delivery is at least once: the same id can arrive more than once.
  • Order is not guaranteed: sale.submitted can arrive before sale.signed.

The Partner Portal lists every delivery with its last status code and error, and can send any delivered or failed event again. A resent event keeps its id.