Skip to content

Webhook signature scheme is implemented but never documented, so non-JS consumers cannot verify #10

Description

@AminDhouib

The gap

The SDK ships verifySignature and constructEvent, so a JavaScript consumer is fine. But the scheme those functions implement is published nowhere:

  • X-Sendly-Signature and X-Sendly-Timestamp do not appear in the OpenAPI spec (https://api.sendly.now/api/openapi.json, all 57 paths, checked 2026-09-03).
  • The webhooks guide at https://docs.sendly.now/guides/webhooks does not mention them.
  • No digest algorithm, no encoding, and no signed-payload construction (what exactly is concatenated, in what order, with what separator) is documented in any of the three places.

The only description is on the /sdks page, which is prose, not a spec.

The practical consequence: a consumer in any language without an official SDK cannot verify a Sendly webhook at all. They have to read sendly-js's source and reverse-engineer the construction — and if the scheme is ever versioned or rotated, their handler breaks with no notice. That is a security surface, not a convenience gap: the alternative most people pick under time pressure is not verifying.

Suggested fix

Document the scheme where it belongs — the webhooks guide, plus a section in this repo's README since this is where people land when they go looking for the implementation:

  • header names and their exact formats
  • the algorithm (HMAC-SHA256, presumably) and encoding (hex/base64)
  • the signed payload string, spelled out character by character
  • the timestamp tolerance the SDK enforces and why
  • whether multiple signatures can appear (key rotation), and how a verifier should handle that
  • a worked example with a fixed secret, payload and expected digest, so an implementation in any language can self-check

A test vector is the highest-value single item there — it is what lets a Go or Ruby consumer confirm they got it right without a live send.

Related

The event-name vocabulary has the same shape of problem: POST /api/webhooks accepts email.sent, email.delivered, email.opened, email.clicked, email.bounced, email.complained, email.failed, contact.created, contact.unsubscribed, contacts.bulk_created, while the webhooks guide documents email.delivery, email.open, email.click, email.bounce, email.complaint, contact.subscribed. A reader who follows the guide subscribes to events that are never sent. Filing here because the SDK's event types are the closest thing to a canonical list.

Context

Found while migrating a production app onto Sendly. Verified 2026-09-03.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions