1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
//! Webhook registration and verification system for the Shopify API SDK.
//!
//! This module provides an in-memory webhook registration system that allows apps
//! to configure webhook subscriptions locally, then sync them with Shopify via
//! GraphQL API mutations using a two-phase pattern. It also provides HMAC-based
//! signature verification for authenticating incoming webhook requests.
//!
//! # Overview
//!
//! The webhook system consists of:
//!
//! ## Registration
//!
//! - [`WebhookRegistry`]: Stores and manages webhook registrations
//! - [`WebhookRegistration`]: Configuration for a single webhook subscription
//! - [`WebhookRegistrationBuilder`]: Builder for creating registrations
//! - [`WebhookRegistrationResult`]: Result of registration operations
//! - [`WebhookDeliveryMethod`]: Delivery method for webhooks (HTTP, EventBridge, Pub/Sub)
//!
//! ## Handler
//!
//! - [`WebhookHandler`]: Trait for implementing webhook handlers
//! - [`BoxFuture`]: Type alias for boxed futures used in handler returns
//!
//! ## Verification
//!
//! - [`WebhookRequest`]: Incoming webhook request data
//! - [`WebhookContext`]: Verified webhook metadata
//! - [`verify_webhook`]: High-level verification with key rotation support
//! - [`verify_hmac`]: Low-level HMAC verification
//!
//! ## Common Types
//!
//! - [`WebhookError`]: Error types for webhook operations
//! - [`WebhookTopic`]: Re-exported webhook topic enum
//!
//! # Two-Phase Registration Pattern
//!
//! The registry follows a two-phase pattern similar to the Ruby SDK:
//!
//! 1. **Add Registration (Local)**: Configure webhooks at app startup using
//! [`WebhookRegistry::add_registration`]
//! 2. **Register with Shopify (Remote)**: Sync with Shopify when a valid session
//! is available using [`WebhookRegistry::register`] or [`WebhookRegistry::register_all`]
//!
//! # Smart Registration
//!
//! The registry performs "smart registration" to minimize API calls:
//! - Queries existing subscriptions from Shopify
//! - Compares configuration to detect changes
//! - Only creates/updates when necessary
//!
//! # Delivery Methods
//!
//! Webhooks can be delivered via three different methods:
//!
//! - **HTTP**: Delivered via HTTP POST to a callback URL
//! - **Amazon EventBridge**: Delivered to an AWS EventBridge event source
//! - **Google Cloud Pub/Sub**: Delivered to a GCP Pub/Sub topic
//!
//! # Webhook Handler Example
//!
//! ```rust
//! use shopify_sdk::webhooks::{
//! WebhookHandler, WebhookContext, WebhookError, WebhookRegistry,
//! WebhookRegistrationBuilder, WebhookTopic, WebhookDeliveryMethod, BoxFuture
//! };
//! use serde_json::Value;
//!
//! // Define a handler
//! struct OrderHandler;
//!
//! impl WebhookHandler for OrderHandler {
//! fn handle<'a>(
//! &'a self,
//! context: WebhookContext,
//! payload: Value,
//! ) -> BoxFuture<'a, Result<(), WebhookError>> {
//! Box::pin(async move {
//! println!("Order webhook from: {:?}", context.shop_domain());
//! Ok(())
//! })
//! }
//! }
//!
//! // Register with a handler using HTTP delivery
//! let mut registry = WebhookRegistry::new();
//! registry.add_registration(
//! WebhookRegistrationBuilder::new(
//! WebhookTopic::OrdersCreate,
//! WebhookDeliveryMethod::Http {
//! uri: "https://example.com/api/webhooks/orders".to_string(),
//! },
//! )
//! .handler(OrderHandler)
//! .build()
//! );
//! ```
//!
//! # Webhook Verification Example
//!
//! ```rust
//! use shopify_sdk::webhooks::{WebhookRequest, verify_webhook, verify_hmac};
//! use shopify_sdk::{ShopifyConfig, ApiKey, ApiSecretKey};
//! use shopify_sdk::auth::oauth::hmac::compute_signature_base64;
//!
//! // Create a config with the API secret
//! let config = ShopifyConfig::builder()
//! .api_key(ApiKey::new("test-key").unwrap())
//! .api_secret_key(ApiSecretKey::new("my-secret").unwrap())
//! .build()
//! .unwrap();
//!
//! // Simulate an incoming webhook
//! let body = b"webhook payload";
//! let hmac = compute_signature_base64(body, "my-secret");
//!
//! // Create a webhook request
//! let request = WebhookRequest::new(
//! body.to_vec(),
//! hmac.clone(),
//! Some("orders/create".to_string()),
//! Some("example.myshopify.com".to_string()),
//! None,
//! None,
//! );
//!
//! // Verify the webhook (high-level)
//! let context = verify_webhook(&config, &request).expect("verification failed");
//! assert_eq!(context.shop_domain(), Some("example.myshopify.com"));
//!
//! // Or use low-level verification for custom integrations
//! assert!(verify_hmac(body, &hmac, "my-secret"));
//! ```
//!
//! # Registration Examples
//!
//! ## HTTP Delivery
//!
//! ```rust
//! use shopify_sdk::webhooks::{
//! WebhookRegistry, WebhookRegistrationBuilder, WebhookTopic, WebhookDeliveryMethod
//! };
//!
//! let mut registry = WebhookRegistry::new();
//!
//! registry.add_registration(
//! WebhookRegistrationBuilder::new(
//! WebhookTopic::OrdersCreate,
//! WebhookDeliveryMethod::Http {
//! uri: "https://example.com/api/webhooks/orders/create".to_string(),
//! },
//! )
//! .include_fields(vec!["id".to_string(), "email".to_string()])
//! .build()
//! );
//! ```
//!
//! ## Amazon EventBridge Delivery
//!
//! ```rust
//! use shopify_sdk::webhooks::{
//! WebhookRegistry, WebhookRegistrationBuilder, WebhookTopic, WebhookDeliveryMethod
//! };
//!
//! let mut registry = WebhookRegistry::new();
//!
//! registry.add_registration(
//! WebhookRegistrationBuilder::new(
//! WebhookTopic::OrdersCreate,
//! WebhookDeliveryMethod::EventBridge {
//! arn: "arn:aws:events:us-east-1::event-source/aws.partner/shopify.com/12345/my-source".to_string(),
//! },
//! )
//! .build()
//! );
//! ```
//!
//! ## Google Cloud Pub/Sub Delivery
//!
//! ```rust
//! use shopify_sdk::webhooks::{
//! WebhookRegistry, WebhookRegistrationBuilder, WebhookTopic, WebhookDeliveryMethod
//! };
//!
//! let mut registry = WebhookRegistry::new();
//!
//! registry.add_registration(
//! WebhookRegistrationBuilder::new(
//! WebhookTopic::ProductsUpdate,
//! WebhookDeliveryMethod::PubSub {
//! project_id: "my-gcp-project".to_string(),
//! topic_id: "shopify-webhooks".to_string(),
//! },
//! )
//! .filter("vendor:MyApp".to_string())
//! .build()
//! );
//!
//! // Later, when you have a session:
//! // let results = registry.register_all(&session, &config).await?;
//! ```
//!
//! # Error Handling
//!
//! ```rust
//! use shopify_sdk::webhooks::{WebhookError, WebhookTopic};
//!
//! fn handle_error(error: WebhookError) {
//! match error {
//! WebhookError::HostNotConfigured => {
//! println!("Please configure host URL in ShopifyConfig");
//! }
//! WebhookError::RegistrationNotFound { topic } => {
//! println!("Topic {:?} not registered locally", topic);
//! }
//! WebhookError::GraphqlError(e) => {
//! println!("API error: {}", e);
//! }
//! WebhookError::ShopifyError { message } => {
//! println!("Shopify error: {}", message);
//! }
//! WebhookError::SubscriptionNotFound { topic } => {
//! println!("Webhook for {:?} not found in Shopify", topic);
//! }
//! WebhookError::InvalidHmac => {
//! println!("Webhook signature verification failed");
//! }
//! WebhookError::NoHandlerForTopic { topic } => {
//! println!("No handler registered for topic: {}", topic);
//! }
//! WebhookError::PayloadParseError { message } => {
//! println!("Failed to parse webhook payload: {}", message);
//! }
//! }
//! }
//! ```
//!
//! # Thread Safety
//!
//! All types in this module are `Send + Sync`, making them safe to share
//! across async tasks.
pub use WebhookError;
pub use WebhookRegistry;
pub use ;
// Verification exports
pub use ;
// Re-export WebhookTopic for convenience
pub use crateWebhookTopic;