Skip to content
Last updated

Webhooks

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.

Configuring Webhook Endpoints

Create an Endpoint

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 secret is only returned on creation and rotation. Store it immediately in your secrets manager. You need it to verify webhook signatures.

Managing Endpoints

OperationMethodEndpoint
List allGET/v3/webhooks/endpoints
Get oneGET/v3/webhooks/endpoints/{id}
UpdatePATCH/v3/webhooks/endpoints/{id}
DeleteDELETE/v3/webhooks/endpoints/{id}

You can disable an endpoint without deleting it by setting status: "disabled" via PATCH.

Receiving Webhooks

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.

Manual Signature Verification

If you are not using the SDKs, implement verification manually following the Standard Webhooks specification:

  1. Build the signed content: {webhook-id}.{webhook-timestamp}.{raw_body}
  2. Decode the secret: remove the whsec_ prefix and base64-decode the remainder
  3. Compute the HMAC-SHA256 of the signed content using the decoded secret
  4. Base64-encode the result and compare it against each signature in the webhook-signature header
  5. Reject if the webhook-timestamp is older than 5 minutes (replay protection)

See the Standard Webhooks specification for full details on headers and signature format.

Retry Policy

Failed deliveries (non-2xx responses or timeouts) are retried with exponential backoff:

AttemptInterval
11 minute
25 minutes
315 minutes
41 hour
56 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-id header as an idempotency key to deduplicate deliveries on your side. The same event may be delivered more than once during retries.

Secret Rotation

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.

  1. Call POST /v3/webhooks/endpoints/{id}/secret/rotate with a transition period
  2. Update your application with the new secret from the response
  3. Deploy -- during the transition period, both secrets work
  4. After the transition period, only the new secret is used

Best Practices

  1. Always verify signatures -- never trust webhook payloads without verification
  2. Respond quickly -- return a 200 status within 5 seconds. Process the event asynchronously if needed.
  3. Implement idempotency -- use the webhook-id to deduplicate. Your handler should safely process the same event twice.
  4. Use HTTPS -- webhook URLs must use HTTPS. HTTP endpoints are rejected.
  5. Log deliveries -- store the webhook-id and webhook-timestamp for debugging and audit trails.

Available Events

All payloads follow the same envelope:

{
  "type": "resource.action",
  "timestamp": "2026-03-04T10:00:00Z",
  "data": { ... }
}

Wildcard Subscription

  • Specific events: ["customer.active", "loan.created"]
  • All from a resource: ["loan.*"]
  • All events: omit the events field or pass an empty array

Events by Resource

ResourceEventDescription
Customercustomer.createdNew customer registered
customer.under_reviewKYC submitted, review started
customer.kyc_updatedKYC requirement status changed
customer.activeFully verified, eligible for credit
Credit Offercredit_offer.availableNew offer created for the customer
credit_offer.expiredOffer passed its expiration date
Loanloan.createdLoan application accepted
loan.contract_generatedCCB contract generated
loan.awaiting_signaturessigning_url available for signing
loan.signedContract fully signed
loan.disbursingFund transfer initiated
loan.activeFunds disbursed, repayment started
loan.payment_receivedInstallment received
loan.finishedLoan fully repaid
loan.cancelledLoan cancelled
loan.errorProcessing or disbursement error