Skip to content

Guide

eSigKit’s webhook surface today is inbound only — we receive Stripe events to drive subscription state. Outbound webhooks (eSigKit emitting events to your endpoints, e.g., signature.rendered, audit.event) are roadmap and not currently implemented. This page documents the inbound Stripe path so self-hosted Stripe accounts integrating with eSigKit know what to expect.

Stripe → eSigKit

When Stripe events fire on your subscription, Stripe POSTs them to https://api.esigkit.com/v1/webhooks/stripe. The handler verifies the signature, dedups against a 7-day idempotency window, and patches the org’s subscription state in DynamoDB.

This endpoint is registered for you automatically when you subscribe via Checkout — you don’t configure anything Stripe-side.

Request shape

POST /v1/webhooks/stripe HTTP/1.1
Host: api.esigkit.com
Content-Type: application/json
Stripe-Signature: t=1746745260,v1=abc123def...

{
  "id": "evt_test_REPLACEME",
  "type": "invoice.paid",
  "data": { "object": { ... } }
}

Signature verification

HMAC-SHA256 via the stripe-signature header. eSigKit verifies via stripe.webhooks.constructEvent with a 5-minute timestamp tolerance (Stripe SDK default). The shared secret lives in SSM at /esigkit/{stage}/stripe/webhook-secret.

// Self-host equivalent — for reference only; you don't run this on your side
import Stripe from 'stripe';
const event = Stripe.webhooks.constructEvent(rawBody, signature, secret);

Idempotency

Every event is dedup-keyed on event.id in the Idempotency table with a 7-day TTL (covers Stripe’s 3-day retry window plus margin). Re-receiving an event ID returns:

{
  "received": true,
  "deduplicated": true
}

Retry semantics

The handler always returns 200 once the signature verifies. Stripe retries on 5xx, so we deliberately swallow handler-side failures into 200 responses to stop the retry loop — failures go to CloudWatch with the correlation ID for ops follow-up.

{ "received": true, "kind": "patch" }

Event-type catalog

EventWhat eSigKit does
checkout.session.completedMark org as active, set currentPeriodEnd, exit trial.
customer.subscription.createdSet subscriptionStatus, plan, price version, currentPeriodEnd.
customer.subscription.updatedRe-sync plan + status. Trigger recomputeOverLimit() if seat count changes.
customer.subscription.deletedMark org cancelled. Set gracePeriodEndsAt = now + 14d.
invoice.paidMark org active if it was past-due, clear gracePeriodEndsAt.
invoice.payment_failedSet subscriptionStatus = past_due, start 14-day grace. After grace, withDunningCheck starts returning 402 on writes.

Verifying signatures yourself (for testing)

If you’re forwarding eSigKit’s Stripe traffic to a local listener via the Stripe CLI:

stripe listen --forward-to localhost:3000/webhook

The CLI signs forwarded events with a stable test secret you can read from stripe listen output. Use that secret to verify in your local handler.

This isn’t usually necessary — eSigKit’s handler IS the webhook receiver and runs on AWS. But if you’re building a service that mirrors eSigKit-style webhook handling, the pattern is the same.