Skip to main content
The SDK’s webhook surface is intentionally narrow today: a signature verifier you call inside your webhook handler. Endpoint management (create / list / revoke) is dashboard-only — see the Webhooks concept.

Types

WebhookVerifyInput

WebhookVerifyResult

DEFAULT_TOLERANCE_SECONDS

Exported for reference; use the toleranceSeconds field on WebhookVerifyInput to override.

Methods

tally.webhooks.verifySignature(input)

Verifies a tally-signature header against the raw request body.
Returns: WebhookVerifyResult. Synchronous; no HTTP, no allocation beyond the HMAC buffer.

verifySignature(input) (standalone)

The same function, exported directly for consumers who don’t want to instantiate a Tally client just to verify a webhook.
Both forms are identical — pick whichever fits your dependency graph.

tally.webhooks.list()

Returns every webhook endpoint in the API key’s account + mode. Revoked endpoints are included (with revoked_at set) for audit.
Returns: Promise<Webhook[]>.

tally.webhooks.create(input)

Creates a new webhook endpoint. The signing secret is returned in webhook.secret exactly once — store it now; it’s not recoverable later. If you lose it, revoke and recreate.
Parameters Returns: Promise<CreatedWebhook> (a Webhook extended with secret: string).

tally.webhooks.revoke(id)

Revokes a webhook endpoint by id. No further deliveries will be enqueued; deliveries already in-flight are not cancelled. Idempotent: revoking an already-revoked endpoint is a no-op.
Returns: Promise<Webhook> (the revoked webhook with revoked_at set).

Full receiver example

Next.js App Router handler:

Why pass the raw body

The HMAC is computed over the exact bytes Tally signed — <unix_ts>.<body>. Any transformation (parsing, re-stringifying, whitespace normalization) breaks the match. In most frameworks, getting the raw body is await req.text() or equivalent. Don’t call req.json() and then JSON.stringify the result back — round-tripping through your runtime’s JSON parser changes key ordering, whitespace, or both, depending on the runtime. If you’re behind a middleware or framework that consumes the body automatically (Express’s body-parser, for example), you’ll need to opt out for the webhook route. Most frameworks support a “give me the raw bytes” mode for this exact case.

Rejection reasons

When verifySignature returns { ok: false, reason }, the reason tells you exactly what failed: Log the reason — it’s the fastest path to diagnosing webhook failures.

Tolerance window

The default 5-minute tolerance is calibrated for the realities of clock drift across servers and Tally’s retry schedule (the first retry is 1 minute after the initial attempt). Don’t widen it casually:
A wider window means a stolen signature stays valid longer. If you’re seeing legitimate deliveries fail timestamp_out_of_tolerance, fix the clock skew on your receiving server first.

Not yet in the SDK

  • tally.webhooks.deliveries.list() / replay() — delivery log access. Dashboard-driven today.