Webhooks
Receive resource events when checkout, payout, and refund state changes occur.
Overview
Webhooks let your platform react to treasury events without polling. Configure endpoints in Developers → Webhooks. The dashboard now exposes endpoint filtering, delivery inspection, manual replay, and secret rotation so operators do not need to leave the app to manage delivery health.
Event Types
These are the current public event names emitted by the checkout, payout, and refund flows.
| Event | Description |
|---|---|
checkout.completed | A checkout completed and the sale was booked to the ledger |
checkout.failed | A sandbox checkout was failed for decline and failure-path testing |
refund_request.created | A buyer-facing refund request was opened and is waiting for review |
refund_request.completed | A refund request was approved and the refund was completed through Soledgic |
refund_request.rejected | A refund request was rejected without moving funds |
refund_request.cancelled | A pending refund request was cancelled before review |
refund.created | A refund was created inside Soledgic |
sale.refunded | A processor-backed refund finished and the sale is fully reflected as refunded |
payout.created | A payout record was created |
payout.processing | A payout rail outcome is pending reconciliation |
payout.executed | A payout rail reported completion |
payout.failed | A payout rail reported failure |
payout_request.* | A creator payout request was created, reviewed, cancelled, failed, or completed |
dispute.created | A processor dispute was opened for a charge or sale |
dispute.funds_withdrawn | Disputed funds were withdrawn by the processor |
chargeback.created | A chargeback or chargeback-risk event was recorded |
chargeback.funds_withdrawn | Chargeback funds were withdrawn by the processor |
hold.created | Funds were placed on hold with a structured reason code |
hold.released | Held funds were released to the recipient balance |
hold.failed | A hold release failed or could not be completed |
Webhook Payload
Deliveries include a JSON body and signature headers.
Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Soledgic-Signature | t=<unix>,v1=<hex> HMAC-SHA256 signature for payload verification |
X-Soledgic-Event | The event name, such as payout.executed |
X-Soledgic-Delivery-Id | Stable delivery identifier for deduplication and replay handling |
X-Soledgic-Attempt | Current delivery attempt number |
Example Payloads
checkout.completed
{
"event": "checkout.completed",
"data": {
"session_id": "cs_uuid",
"reference_id": "order_12345",
"amount": 5000,
"currency": "USD",
"participant_id": "creator_001",
"transaction_id": "txn_uuid",
"status": "completed",
"occurred_at": "2026-04-27T12:00:00Z"
}
}payout.executed
{
"event": "payout.executed",
"data": {
"payout_id": "6f2f0ac5-4f50-4e96-b412-4f534f1c85c6",
"participant_id": "creator_001",
"amount": 15000,
"currency": "USD",
"rail": "ach",
"external_id": "tr_123",
"status": "completed",
"occurred_at": "2026-04-27T14:30:00Z"
}
}refund.created
{
"event": "refund.created",
"data": {
"refund_id": "txn_uuid",
"original_sale_reference": "order_12345",
"amount": 2500,
"reason": "Customer requested",
"status": "completed",
"occurred_at": "2026-04-27T15:00:00Z"
}
}payout.failed
{
"event": "payout.failed",
"data": {
"payout_id": "6f2f0ac5-4f50-4e96-b412-4f534f1c85c6",
"participant_id": "creator_001",
"amount": 15000,
"error": "Insufficient funds in source account",
"status": "failed",
"occurred_at": "2026-04-27T14:35:00Z"
}
}Verifying Signatures
Always verify the signature before processing the payload.
Important: The webhook secret is shown when the endpoint is created. Store it outside your app code. New Soledgic webhook secrets start with wh_sec_slk.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifySoledgicSignature(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(',').map((part) => part.split('=')),
);
const timestamp = parts.t;
const received = Buffer.from(parts.v1 || '', 'hex');
const expectedHex = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const expected = Buffer.from(expectedHex, 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
}
const rawBody = await request.text();
const signature = request.headers.get('x-soledgic-signature') || '';
const isValid = verifySoledgicSignature(
rawBody,
signature,
process.env.SOLEDGIC_WEBHOOK_SECRET || '',
);
if (!isValid) {
throw new Error('Invalid webhook signature');
}Retry Policy
- Up to 5 retry attempts for non-2xx responses
- Exponential backoff between attempts, capped at 4 hours
- HTTP 429 responses are slowed down to at least 5 minutes before retry
- Failed deliveries stay visible in webhook delivery history, including payload and response body
- Operators can manually replay exhausted deliveries from Developers → Webhooks
Operations
The webhook settings screen is designed for production operations, not just initial setup.
- Filter delivery history by endpoint, event, status, checkout ID, order ID, or payment ID
- Inspect payloads, headers, attempts, and downstream response bodies
- Replay stuck or exhausted deliveries after downstream fixes
- Rotate endpoint secrets; the previous secret remains valid only during the short rotation grace window
Best Practices
Return 200 quickly
Acknowledge the delivery, then process work asynchronously
Deduplicate by event ID
Webhook delivery retries are normal and should be safe
Verify signatures
Never trust the body before checking the HMAC signature