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.

Response Codes

Your webhook endpoint must respond within 15 seconds. Only the HTTP status code matters -- the response body is ignored.

StatusBehaviorDescription
2xx (200-299)SuccessDelivery confirmed. Most common: 200 OK or 204 No Content.
3xx (300-399)Failure + retryRedirects are NOT followed. Update the endpoint URL via API instead.
410 GoneEndpoint disabledDinie permanently disables the endpoint and stops sending events. Use to unsubscribe without calling the API.
429 Too Many RequestsThrottleDinie reduces send rate and reschedules with backoff.
4xx (other)Failure + retryAny other client error triggers retries.
502 Bad GatewayThrottleDinie reduces send rate before retrying.
504 Gateway TimeoutThrottleSame behavior as 502.
5xx (other)Failure + retryAny other server error triggers retries.
Timeout (no response in 15s)Failure + retryConnection closed, delivery treated as failure.

Retry Policy

Failed deliveries are retried with exponential backoff. Random jitter is added to each interval to prevent thundering herd.

AttemptInterval
1Immediate
25 seconds
35 minutes
430 minutes
52 hours
65 hours
710 hours
814 hours
920 hours
1024 hours

After 10 failed attempts (~3 days), the event is marked as failed. Failed events can be resent manually. 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 15 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": 1709546400,
  "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 (status: creating)
customer.under_reviewKYC submitted, review started
customer.kyc_updatedKYC requirement status changed
customer.activeFully verified, registration complete
customer.deniedPermanently blocked (fraud, closed company)
Credit Offercredit_offer.availableNew offer created for the customer
credit_offer.expiredOffer passed its expiration date
Loanloan.createdLoan created with CCB, awaiting signatures
loan.signature_receivedIndividual signer completed their signature
loan.processingAll signatures collected, disbursement in progress
loan.activeFunds disbursed, repayment started
loan.payment_receivedInstallment received
loan.finishedLoan fully repaid
loan.cancelledLoan cancelled
loan.errorProcessing or disbursement error