AXIAM C++ SDK 1.0.0-alpha8
Authentication, authorization, JWKS & route guards (REST + mTLS)
Loading...
Searching...
No Matches
webhook.hpp
Go to the documentation of this file.
1// §13 Webhook signature verification (T-145).
2//
3// AXIAM signs every webhook delivery with a Stripe-style signed timestamp:
4//
5// X-Axiam-Timestamp : <unix seconds, decimal ASCII>
6// X-Axiam-Signature : t=<unix seconds>,v1=<hex lowercase HMAC-SHA256>
7// X-Axiam-Event : <event type>
8// X-Axiam-Delivery : <delivery UUID>
9//
10// where `v1 = HMAC-SHA256(secret_utf8_bytes, "<t>.<raw_body>")`.
11//
12// !! The body MUST be the exact raw bytes received off the wire. !!
13// Never re-serialize parsed JSON before verifying — a change in key order or
14// whitespace changes the MAC and the signature will not match. Capture the body
15// before your framework's JSON parser touches it.
16//
17// Dedup is the receiver's job: `X-Axiam-Delivery` is the at-least-once dedup
18// key. A retry replays a byte-identical, still-valid signature inside the
19// freshness window, so keep a short-lived seen-set of delivery ids if your
20// handler is not idempotent.
21#pragma once
22
23#include <chrono>
24#include <cstdint>
25#include <functional>
26#include <optional>
27#include <string>
28
29#include "axiam/errors.hpp"
30#include "axiam/sensitive.hpp"
31
32namespace axiam {
33namespace webhook {
34
47
49const char* to_string(VerifyError error) noexcept;
50
52struct Event {
54 std::int64_t timestamp = 0;
56 std::string event_type;
59 std::string delivery_id;
61 std::string body;
62};
63
65struct Options {
69 std::chrono::seconds tolerance{300};
70
73 std::function<std::int64_t()> now;
74
78 std::optional<std::string> timestamp_header;
79
81 std::string event_type;
82
84 std::string delivery_id;
85};
86
88struct Result {
89 bool ok = false;
92
93 explicit operator bool() const noexcept { return ok; }
94 const char* error_message() const noexcept { return to_string(error); }
95};
96
99public:
100 VerifyException(VerifyError error, const std::string& message)
101 : AxiamError(message), error_(error) {}
102 VerifyError error() const noexcept { return error_; }
103
104private:
105 VerifyError error_;
106};
107
114Result verify(const Sensitive<std::string>& secret, const std::string& signature_header,
115 const std::string& body, const Options& options = Options{});
116
118Result verify(const std::string& secret, const std::string& signature_header,
119 const std::string& body, const Options& options = Options{});
120
124Event verify_or_throw(const Sensitive<std::string>& secret, const std::string& signature_header,
125 const std::string& body, const Options& options = Options{});
126
127} // namespace webhook
128} // namespace axiam
Base class for every error the SDK raises. Never carries raw token material.
Definition errors.hpp:12
Wraps secret material (access tokens, mTLS private keys).
Definition sensitive.hpp:26
Thrown by verify_or_throw() when a delivery does not verify.
Definition webhook.hpp:98
VerifyError error() const noexcept
Definition webhook.hpp:102
VerifyException(VerifyError error, const std::string &message)
Definition webhook.hpp:100
Event verify_or_throw(const Sensitive< std::string > &secret, const std::string &signature_header, const std::string &body, const Options &options=Options{})
Throwing twin of verify(), for handlers that prefer an exception.
VerifyError
Why verification failed.
Definition webhook.hpp:37
@ kMalformedTimestamp
t= was not a non-negative decimal integer
@ kTimestampOutOfTolerance
|now - t| exceeded the freshness window
@ kMalformedHeader
unparseable, empty, or no/duplicate t=
@ kTimestampHeaderMismatch
X-Axiam-Timestamp disagreed with t=
@ kMissingSignature
header parsed but carried no v1= value
@ kSignatureMismatch
no supplied v1 matched the computed MAC
@ kEmptySecret
no secret configured — fail closed, never skip
const char * to_string(VerifyError error) noexcept
Stable, secret-free description of a VerifyError.
Result verify(const Sensitive< std::string > &secret, const std::string &signature_header, const std::string &body, const Options &options=Options{})
Verify a webhook delivery.
Definition authenticator.hpp:40
A verified delivery.
Definition webhook.hpp:52
std::int64_t timestamp
The t= value covered by the MAC.
Definition webhook.hpp:54
std::string delivery_id
X-Axiam-Delivery, when supplied to verify() via Options.
Definition webhook.hpp:59
std::string event_type
X-Axiam-Event, when supplied to verify() via Options.
Definition webhook.hpp:56
std::string body
The raw body that was verified.
Definition webhook.hpp:61
Optional inputs to verify().
Definition webhook.hpp:65
std::chrono::seconds tolerance
Two-sided freshness window.
Definition webhook.hpp:69
std::string event_type
X-Axiam-Event, copied into the returned Event. Not covered by the MAC.
Definition webhook.hpp:81
std::string delivery_id
X-Axiam-Delivery, copied into the returned Event. Not covered by the MAC.
Definition webhook.hpp:84
std::optional< std::string > timestamp_header
The separate X-Axiam-Timestamp header, if the receiver read it.
Definition webhook.hpp:78
std::function< std::int64_t()> now
Time source in unix seconds.
Definition webhook.hpp:73
Outcome of verify(). Contextually convertible to bool; falsy means rejected.
Definition webhook.hpp:88
Event event
Definition webhook.hpp:91
const char * error_message() const noexcept
Definition webhook.hpp:94
VerifyError error
Definition webhook.hpp:90
bool ok
Definition webhook.hpp:89