Duffel.Webhooks (Duffel v0.1.0)

Copy Markdown View Source

Manage webhook subscriptions and verify incoming webhook signatures.

Receiving webhooks

Duffel signs every webhook delivery with the X-Duffel-Signature header. Verify it against the raw (unparsed) request body before trusting the payload:

case Duffel.Webhooks.verify_signature(signature_header, raw_body, secret) do
  :ok -> handle_event(Jason.decode!(raw_body))
  {:error, reason} -> reject(reason)
end

See the Duffel documentation and the receiving webhooks guide.

Summary

Functions

Creates a webhook subscription.

Deletes a webhook subscription.

Lists one page of webhooks.

Sends a test (ping) event to the webhook's URL.

Lazily streams all webhooks across pages.

Updates a webhook's URL, subscribed events or active status.

Verifies the X-Duffel-Signature header of a webhook delivery.

Functions

create(client, params, opts \\ [])

@spec create(Duffel.Client.t(), map(), keyword()) ::
  {:ok, map()} | {:error, Duffel.Error.t()}

Creates a webhook subscription.

The response includes the shared secret used to sign deliveries — it is only returned on creation, so store it.

Examples

Duffel.Webhooks.create(client, %{
  url: "https://example.com/webhooks/duffel",
  events: ["order.created", "order.airline_initiated_change_detected"]
})

delete(client, id)

@spec delete(Duffel.Client.t(), String.t()) :: :ok | {:error, Duffel.Error.t()}

Deletes a webhook subscription.

list(client, params \\ [])

@spec list(Duffel.Client.t(), keyword() | map()) ::
  {:ok, Duffel.Page.t()} | {:error, Duffel.Error.t()}

Lists one page of webhooks.

Parameters

  • :limit / :after / :before - pagination (see Duffel.Page)

ping(client, id)

@spec ping(Duffel.Client.t(), String.t()) :: :ok | {:error, Duffel.Error.t()}

Sends a test (ping) event to the webhook's URL.

stream(client, params \\ [])

@spec stream(Duffel.Client.t(), keyword() | map()) :: Enumerable.t()

Lazily streams all webhooks across pages.

Raises Duffel.Error if a page request fails.

update(client, id, params)

@spec update(Duffel.Client.t(), String.t(), map()) ::
  {:ok, map()} | {:error, Duffel.Error.t()}

Updates a webhook's URL, subscribed events or active status.

Examples

Duffel.Webhooks.update(client, "sev_123", %{active: false})

verify_signature(signature_header, raw_body, secret, opts \\ [])

@spec verify_signature(String.t() | nil, binary(), String.t(), keyword()) ::
  :ok
  | {:error, :invalid_format | :invalid_signature | :timestamp_out_of_tolerance}

Verifies the X-Duffel-Signature header of a webhook delivery.

The header has the form t=<unix timestamp>,v1=<signature>, where the signature is the lowercase hex HMAC-SHA256 of "<timestamp>.<raw body>" keyed with the webhook's shared secret. raw_body must be the exact bytes received — verify before any JSON parsing or re-encoding.

Comparison is constant-time. Deliveries older than :tolerance seconds (default 300) are rejected to limit replay attacks; pass tolerance: :infinity to disable the timestamp check.

Options

  • :tolerance - max allowed age in seconds, or :infinity (default 300)
  • :now - current unix time in seconds, for testing (default System.system_time(:second))

Return values

  • :ok - signature is valid
  • {:error, :invalid_format} - header missing or malformed
  • {:error, :invalid_signature} - signature does not match
  • {:error, :timestamp_out_of_tolerance} - delivery too old