Skip to documentation content

Webhooks

Verify provider events in the merchant backend and verify outbound Carden events with a separate HMAC boundary.

On this page

Separate event directions

FlowVerifierPurpose
Stripe or Square → merchantMerchant backend verifies the provider signature.Observe actual later payment changes and create normalized Carden reports.
Carden → merchant endpointMerchant endpoint verifies Carden HMAC.Receive configured notifications about Carden activity.

Handle provider events

  1. Read the raw provider request and verify it with the provider's supported routine.
  2. Correlate the verified event to a stable attempt and provider object IDs.
  3. Retrieve current provider state when notification order or content is ambiguous.
  4. Persist a normalized report with new eventId, actual occurredAt, cumulative amounts, source: merchant_webhook, and providerEventId.
  5. Deliver it from a durable worker with payments:write.

Send only allowlisted report fields. Raw provider objects can contain credentials, client secrets, customer data, or unrestricted metadata and do not belong in Carden reports.

Outbound Carden events

TypeMeaning
payment.reportedA merchant observation was recorded.
enrichment.preparedProvider context was prepared.
integration.syncedIntegration sync finished; inspect status and exception counts.
integration.failedAn integration operation failed.
team.updated / workspace.updatedAccess or workspace configuration changed.
webhook.testA test delivery from the saved destination control.

For integration.synced, inspect status, recordsRejected, and reconciliationExceptions. partially_succeeded is complete but still needs review. payment.reported means reported activity, and enrichment.prepared does not certify provider acceptance or savings.

The event envelope contains id, type, createdAt, environment, and event-specific data. Body id equals x-carden-event-id. Validate type and context and tolerate additional fields defensively.

Verify the Carden signature

Delivery headers
x-carden-signature: t=<unix-seconds>,v1=<hex-hmac-sha256>
x-carden-event-id: <event-id>

Compute HMAC-SHA256 with the webhook secret over timestamp + '.' + unmodified raw body. Parse exactly one t and one v1 value, validate their formats, apply an explicit timestamp tolerance with a synchronized clock, and compare decoded signatures in constant time before JSON parsing or side effects.

Node.js verification core
import { createHmac, timingSafeEqual } from "node:crypto";

const expected = createHmac("sha256", secret)
  .update(timestamp + ".")
  .update(rawBody)
  .digest();
const actual = Buffer.from(signatureV1, "hex");
const valid = actual.length === expected.length && timingSafeEqual(actual, expected);

Never reconstruct JSON with JSON.stringify before verification. Whitespace and byte changes alter the signature. The webhook signing secret is separate from a Carden API key.

Process idempotently and recover

  • Validate signature, event type, and merchant context.
  • Deduplicate using the verified body ID and confirm it equals x-carden-event-id.
  • Persist or enqueue verified work before success response.
  • Tolerate duplicate and out-of-order notification.
  • Provide reconciliation that does not depend on webhooks as the only record.
Delivery boundaryCurrent behavior
DestinationPublic HTTPS on port 443 only, without URL credentials or fragments; DNS is revalidated to public addresses and redirects are refused.
RetryAt most six delivery attempts with exponential delays beginning at 30 seconds.
Secret rotationPending retries use the destination's current secret while immutable body and event ID stay unchanged.
SubscriptionEnabled selected event types from the destination's organization and environment.

No long-term replay window or strict ordering guarantee is asserted here. Build idempotent consumers and recover through reconciliation.