Webhooks deliver real-time notifications when resources change. Instead of polling the API, register a URL and receive POST requests whenever events occur. The Dinie API follows the Standard Webhooks specification.
Register a URL to receive webhook events. You can subscribe to specific events or receive all of them.
const endpoint = await dinie.webhooks.endpoints.create({
url: "https://yourapp.example.com/webhooks/dinie",
events: ["customer.active", "credit_offer.available", "loan.*"],
description: "Production webhook",
});
// Store endpoint.secret securely -- it is only displayed once.
console.log(endpoint.secret); // "whsec_xxxxx..."The returned endpoint.secret is required to verify signatures -- store it immediately in your secrets manager.
Warning: The
secretis only returned on creation and rotation. Store it immediately in your secrets manager. You need it to verify webhook signatures.
| Operation | Method | Endpoint |
|---|---|---|
| List all | GET | /v3/webhooks/endpoints |
| Get one | GET | /v3/webhooks/endpoints/{id} |
| Update | PATCH | /v3/webhooks/endpoints/{id} |
| Delete | DELETE | /v3/webhooks/endpoints/{id} |
You can disable an endpoint without deleting it by setting status: "disabled" via PATCH.
Each delivery is a POST request with authentication headers (webhook-id, webhook-timestamp, webhook-signature). Use the SDK to verify and parse the event:
import express from "express";
import Dinie from "dinie";
const app = express();
const dinie = new Dinie({
clientId: process.env.DINIE_CLIENT_ID,
clientSecret: process.env.DINIE_CLIENT_SECRET,
webhookSecret: process.env.DINIE_WEBHOOK_SECRET,
});
app.post("/webhooks/dinie", express.raw({ type: "application/json" }), (req, res) => {
const event = dinie.webhooks.unwrap(req.body.toString(), req.headers);
switch (event.type) {
case "customer.active":
console.log(`Customer ${event.data.id} approved`);
break;
case "loan.active":
console.log(`Loan ${event.data.id} disbursed`);
break;
}
res.sendStatus(200);
});The webhook_secret is configured in the client constructor. The unwrap method uses this secret to verify the HMAC-SHA256 signature, validate the timestamp (5-minute replay protection), and return the typed event. If verification fails, an exception is thrown -- return 400 in that case.
If you are not using the SDKs, implement verification manually following the Standard Webhooks specification:
- Build the signed content:
{webhook-id}.{webhook-timestamp}.{raw_body} - Decode the secret: remove the
whsec_prefix and base64-decode the remainder - Compute the HMAC-SHA256 of the signed content using the decoded secret
- Base64-encode the result and compare it against each signature in the
webhook-signatureheader - Reject if the
webhook-timestampis older than 5 minutes (replay protection)
See the Standard Webhooks specification for full details on headers and signature format.
Failed deliveries (non-2xx responses or timeouts) are retried with exponential backoff:
| Attempt | Interval |
|---|---|
| 1 | 1 minute |
| 2 | 5 minutes |
| 3 | 15 minutes |
| 4 | 1 hour |
| 5 | 6 hours |
After 5 failed attempts, the event is marked as failed. Repeated failures across multiple events will automatically disable the webhook endpoint.
Info: Use the
webhook-idheader as an idempotency key to deduplicate deliveries on your side. The same event may be delivered more than once during retries.
Rotate your webhook signing secret without downtime:
curl -X POST https://sandbox.api.dinie.com.br/v3/webhooks/endpoints/we_550e8400.../secret/rotate \
-H "Authorization: Bearer dinie_at_..." \
-H "Content-Type: application/json" \
-d '{ "expire_current_in": 3600 }'During the transition period (expire_current_in, default 1 hour, maximum 24 hours), both secrets -- old and new -- are active. The webhook-signature header contains signatures for both secrets, so your verification code will match either one.
- Call
POST /v3/webhooks/endpoints/{id}/secret/rotatewith a transition period - Update your application with the new secret from the response
- Deploy -- during the transition period, both secrets work
- After the transition period, only the new secret is used
- Always verify signatures -- never trust webhook payloads without verification
- Respond quickly -- return a
200status within 5 seconds. Process the event asynchronously if needed. - Implement idempotency -- use the
webhook-idto deduplicate. Your handler should safely process the same event twice. - Use HTTPS -- webhook URLs must use HTTPS. HTTP endpoints are rejected.
- Log deliveries -- store the
webhook-idandwebhook-timestampfor debugging and audit trails.
All payloads follow the same envelope:
{
"type": "resource.action",
"timestamp": "2026-03-04T10:00:00Z",
"data": { ... }
}- Specific events:
["customer.active", "loan.created"] - All from a resource:
["loan.*"] - All events: omit the
eventsfield or pass an empty array
| Resource | Event | Description |
|---|---|---|
| Customer | customer.created | New customer registered |
customer.under_review | KYC submitted, review started | |
customer.kyc_updated | KYC requirement status changed | |
customer.active | Fully verified, eligible for credit | |
| Credit Offer | credit_offer.available | New offer created for the customer |
credit_offer.expired | Offer passed its expiration date | |
| Loan | loan.created | Loan application accepted |
loan.contract_generated | CCB contract generated | |
loan.awaiting_signatures | signing_url available for signing | |
loan.signed | Contract fully signed | |
loan.disbursing | Fund transfer initiated | |
loan.active | Funds disbursed, repayment started | |
loan.payment_received | Installment received | |
loan.finished | Loan fully repaid | |
loan.cancelled | Loan cancelled | |
loan.error | Processing or disbursement error |