Skip to content

Webhooks

The Webhooks module provides a standard interface for validating inbound webhook signatures from external providers. It includes HmacSignature utilities for timing-safe HMAC computation and per-provider validator implementations.

Quick Start

using Deepstaging.Webhooks;

// Validate a webhook in a handler
[Webhook<SlackWebhookValidator>]
[HttpPost("/webhooks/slack")]
public static Eff<AppRuntime, Unit> HandleSlackEvent(SlackEvent evt) =>
    // Webhook signature is validated before this handler runs
    from _ in processEvent(evt)
    select unit;

Features

Feature Description
IWebhookValidator Core interface — Validate(WebhookValidationContext)
WebhookValidationContext Request signature, raw body, headers, and provider metadata
HmacSignature Static utility — ComputeSha256Hex, ComputeSha256Base64, ComputeSha1Base64, TimeSafeEquals
[Webhook<T>] Attribute on dispatch handlers — auto-validates before handler runs
Provider validators Slack, Stripe, Twilio, Bandwidth, SNS

Core Interface

public interface IWebhookValidator
{
    bool Validate(WebhookValidationContext context);
}

HmacSignature Utilities

All HMAC computations use timing-safe comparison to prevent timing attacks:

// Compute HMAC-SHA256 hex
var signature = HmacSignature.ComputeSha256Hex(secret, payload);

// Compute HMAC-SHA256 base64
var signature = HmacSignature.ComputeSha256Base64(secret, payload);

// Compute HMAC-SHA1 base64 (for legacy providers)
var signature = HmacSignature.ComputeSha1Base64(secret, payload);

// Timing-safe comparison
var isValid = HmacSignature.TimeSafeEquals(expected, actual);

Provider Validators

Provider Validator Class Signature Scheme
Slack SlackWebhookValidator HMAC-SHA256 with v0= prefix and timestamp
Stripe StripeWebhookValidator HMAC-SHA256 with t= timestamp
Twilio TwilioWebhookValidator HMAC-SHA1 base64 of URL + params
Bandwidth BandwidthWebhookValidator Basic auth validation
Amazon SNS SnsWebhookValidator Certificate-based signature verification

Implementing a Custom Validator

public sealed class MyProviderWebhookValidator(MyProviderConfig config) : IWebhookValidator
{
    public bool Validate(WebhookValidationContext context)
    {
        var expected = HmacSignature.ComputeSha256Hex(
            config.WebhookSecret,
            context.RawBody);

        return HmacSignature.TimeSafeEquals(expected, context.Signature);
    }
}

Templating

When sending outbound webhooks to third-party consumers, use Scriban templates to define customizable JSON payload formats. This lets consumers configure the shape of webhook payloads without code changes.

// Templates/Webhooks/OrderEvent.scriban-json
{
  "event": "{{ event_type }}",
  "timestamp": "{{ timestamp | date.to_string "%Y-%m-%dT%H:%M:%SZ" }}",
  "data": {
    "order_id": "{{ order.id }}",
    "status": "{{ order.status }}",
    "total": {{ order.total }}
    {{- if order.tracking_url }},
    "tracking_url": "{{ order.tracking_url }}"
    {{- end }}
  }
}

For syntax reference and best practices, see Scriban Templating.

Sub-Pages

Page Description
Attributes [Webhook<T>] attribute, WebhookValidationContext fields, dispatch integration

Source

src/Core/Deepstaging.Runtime/Webhooks/