pub struct WebhookEnvelope {
pub schema: u8,
pub source_id: String,
pub event_kind: String,
pub body_json: Value,
pub headers_subset: BTreeMap<String, String>,
pub received_at_ms: i64,
pub envelope_id: Uuid,
pub client_ip: Option<IpAddr>,
}Expand description
Typed JSON envelope nexo publishes after every accepted webhook request.
Subscribers correlate events via envelope_id (deterministic
dedup) and received_at_ms (late-binding analytics).
headers_subset is a defensive allowlist — secrets like
Authorization / Cookie / signature headers are stripped
before publish, so a NATS subscriber sees only non-secret
correlation IDs.
Unlike crate::BindingContext, this struct is intentionally
not #[non_exhaustive]: it represents a wire-shape value
constructed on both sides (the daemon writes it; tests +
mocks build it via struct-literal). Field additions are
semver-major because the JSON wire shape changes regardless.
§Example
Microapps typically deserialise the envelope from a NATS payload:
use nexo_tool_meta::WebhookEnvelope;
let payload = serde_json::json!({
"schema": 1,
"source_id": "github_main",
"event_kind": "pull_request",
"body_json": {"action": "opened"},
"headers_subset": {},
"received_at_ms": 0,
"envelope_id": "00000000-0000-0000-0000-000000000000",
"client_ip": null
});
let env: WebhookEnvelope = serde_json::from_value(payload).unwrap();
assert_eq!(env.schema, 1);
assert_eq!(env.source_id, "github_main");Fields§
§schema: u8Wire-shape version. Always ENVELOPE_SCHEMA_VERSION.
source_id: StringOperator-assigned source identifier — matches the
webhook_receiver.sources[].id YAML field.
event_kind: StringEvent kind extracted from the inbound request (header or JSON body path, per source config).
body_json: ValueInbound body, parsed as JSON. Non-JSON bodies are wrapped
as { "raw_base64": "..." } upstream.
headers_subset: BTreeMap<String, String>Allowlisted headers forwarded for downstream correlation. Authorization / Cookie / signature headers are stripped.
received_at_ms: i64Server-side receipt timestamp in milliseconds since epoch.
envelope_id: UuidRandom per-envelope identifier — useful for dedup.
client_ip: Option<IpAddr>Resolved client IP (after trusted-proxy logic). None
for envelopes built outside an HTTP request context.
Trait Implementations§
Source§impl Clone for WebhookEnvelope
impl Clone for WebhookEnvelope
Source§fn clone(&self) -> WebhookEnvelope
fn clone(&self) -> WebhookEnvelope
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for WebhookEnvelope
impl Debug for WebhookEnvelope
Source§impl<'de> Deserialize<'de> for WebhookEnvelope
impl<'de> Deserialize<'de> for WebhookEnvelope
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl PartialEq for WebhookEnvelope
impl PartialEq for WebhookEnvelope
Source§fn eq(&self, other: &WebhookEnvelope) -> bool
fn eq(&self, other: &WebhookEnvelope) -> bool
self and other values to be equal, and is used by ==.Source§impl Serialize for WebhookEnvelope
impl Serialize for WebhookEnvelope
Source§impl TS for WebhookEnvelope
impl TS for WebhookEnvelope
Source§type WithoutGenerics = WebhookEnvelope
type WithoutGenerics = WebhookEnvelope
WithoutGenerics should just be Self.
If the type does have generic parameters, then all generic parameters must be replaced with
a dummy type, e.g ts_rs::Dummy or (). The only requirement for these dummy types is that
EXPORT_TO must be None. Read moreSource§type OptionInnerType = WebhookEnvelope
type OptionInnerType = WebhookEnvelope
std::option::Option<T>, then this associated type is set to T.
All other implementations of TS should set this type to Self instead.Source§fn docs() -> Option<String>
fn docs() -> Option<String>
TS is derived, docs are
automatically read from your doc comments or #[doc = ".."] attributesSource§fn decl_concrete(cfg: &Config) -> String
fn decl_concrete(cfg: &Config) -> String
TS::decl().
If this type is not generic, then this function is equivalent to TS::decl().Source§fn decl(cfg: &Config) -> String
fn decl(cfg: &Config) -> String
type User = { user_id: number, ... }.
This function will panic if the type has no declaration. Read moreSource§fn inline(cfg: &Config) -> String
fn inline(cfg: &Config) -> String
{ user_id: number }.
This function will panic if the type cannot be inlined.Source§fn inline_flattened(cfg: &Config) -> String
fn inline_flattened(cfg: &Config) -> String
Source§fn visit_generics(v: &mut impl TypeVisitor)where
Self: 'static,
fn visit_generics(v: &mut impl TypeVisitor)where
Self: 'static,
Source§fn output_path() -> Option<PathBuf>
fn output_path() -> Option<PathBuf>
T should be exported, relative to the output directory.
The returned path does not include any base directory. Read moreSource§fn visit_dependencies(v: &mut impl TypeVisitor)where
Self: 'static,
fn visit_dependencies(v: &mut impl TypeVisitor)where
Self: 'static,
Source§fn dependencies(cfg: &Config) -> Vec<Dependency>where
Self: 'static,
fn dependencies(cfg: &Config) -> Vec<Dependency>where
Self: 'static,
Source§fn export(cfg: &Config) -> Result<(), ExportError>where
Self: 'static,
fn export(cfg: &Config) -> Result<(), ExportError>where
Self: 'static,
TS::export_all. Read moreSource§fn export_all(cfg: &Config) -> Result<(), ExportError>where
Self: 'static,
fn export_all(cfg: &Config) -> Result<(), ExportError>where
Self: 'static,
TS::export. Read more