Webhooks
January 27, 2026 ยท View on GitHub
Webhooks allow your app to receive real-time notifications when events occur in a Shopify store. This guide covers registration, delivery methods, and verification.
Table of Contents
- Overview
- Two-Phase Registration
- Delivery Methods
- Registration Options
- Webhook Handlers
- Verification
- Best Practices
Overview
The webhook system consists of two main components:
- Registration - Configure which events to subscribe to and where to deliver them
- Handling - Verify and process incoming webhook requests
Two-Phase Registration
The SDK uses a two-phase registration pattern:
- Configure locally - Add registrations at app startup
- Sync with Shopify - Register with Shopify when you have a valid session
use shopify_sdk::{
WebhookRegistry, WebhookRegistrationBuilder, WebhookTopic, WebhookDeliveryMethod
};
// Phase 1: Configure at startup
let mut registry = WebhookRegistry::new();
registry.add_registration(
WebhookRegistrationBuilder::new(
WebhookTopic::OrdersCreate,
WebhookDeliveryMethod::Http {
uri: "https://example.com/webhooks/orders/create".to_string(),
},
)
.build()
);
registry.add_registration(
WebhookRegistrationBuilder::new(
WebhookTopic::ProductsUpdate,
WebhookDeliveryMethod::Http {
uri: "https://example.com/webhooks/products/update".to_string(),
},
)
.build()
);
// Phase 2: Register with Shopify (when session is available)
let results = registry.register_all(&session, &config).await?;
for result in &results {
println!("Topic {:?}: success={}", result.topic, result.success);
}
Delivery Methods
Webhooks can be delivered via HTTP, Amazon EventBridge, or Google Cloud Pub/Sub.
HTTP Delivery
Standard HTTP POST to your endpoint:
use shopify_sdk::{
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".to_string(),
},
)
.build()
);
Amazon EventBridge
Deliver webhooks to AWS EventBridge:
use shopify_sdk::{
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/123/source".to_string(),
},
)
.build()
);
Google Cloud Pub/Sub
Deliver webhooks to GCP Pub/Sub:
use shopify_sdk::{
WebhookRegistry, WebhookRegistrationBuilder, WebhookTopic, WebhookDeliveryMethod
};
let mut registry = WebhookRegistry::new();
registry.add_registration(
WebhookRegistrationBuilder::new(
WebhookTopic::CustomersCreate,
WebhookDeliveryMethod::PubSub {
project_id: "my-gcp-project".to_string(),
topic_id: "shopify-webhooks".to_string(),
},
)
.build()
);
Registration Options
The WebhookRegistrationBuilder supports additional options:
Include Fields
Request specific fields in the webhook payload:
registry.add_registration(
WebhookRegistrationBuilder::new(
WebhookTopic::OrdersCreate,
WebhookDeliveryMethod::Http {
uri: "https://example.com/webhooks".to_string(),
},
)
.include_fields(vec![
"id".to_string(),
"email".to_string(),
"total_price".to_string(),
])
.build()
);
Metafield Namespaces
Include specific metafield namespaces:
registry.add_registration(
WebhookRegistrationBuilder::new(
WebhookTopic::ProductsUpdate,
WebhookDeliveryMethod::Http {
uri: "https://example.com/webhooks".to_string(),
},
)
.metafield_namespaces(vec!["custom".to_string(), "my_app".to_string()])
.build()
);
Filters
Filter which events trigger the webhook:
registry.add_registration(
WebhookRegistrationBuilder::new(
WebhookTopic::ProductsCreate,
WebhookDeliveryMethod::Http {
uri: "https://example.com/webhooks".to_string(),
},
)
.filter("vendor:MyVendor".to_string())
.build()
);
Webhook Handlers
Define handlers to process incoming webhooks:
use shopify_sdk::webhooks::{
WebhookHandler, WebhookContext, WebhookError, WebhookRegistry,
WebhookRegistrationBuilder, WebhookTopic, WebhookDeliveryMethod, BoxFuture
};
use serde_json::Value;
// Define a handler struct
struct OrderCreatedHandler;
impl WebhookHandler for OrderCreatedHandler {
fn handle<'a>(
&'a self,
context: WebhookContext,
payload: Value,
) -> BoxFuture<'a, Result<(), WebhookError>> {
Box::pin(async move {
let shop = context.shop_domain().unwrap_or("unknown");
let topic = context.topic().unwrap_or("unknown");
println!("Received {} from {}", topic, shop);
println!("Order ID: {}", payload["id"]);
// Process the order...
Ok(())
})
}
}
// Register with a handler
let mut registry = WebhookRegistry::new();
registry.add_registration(
WebhookRegistrationBuilder::new(
WebhookTopic::OrdersCreate,
WebhookDeliveryMethod::Http {
uri: "https://example.com/webhooks/orders".to_string(),
},
)
.handler(OrderCreatedHandler)
.build()
);
Verification
Always verify incoming webhooks using HMAC signatures.
High-Level Verification
Use verify_webhook for complete verification with key rotation support:
use shopify_sdk::webhooks::{WebhookRequest, verify_webhook};
use shopify_sdk::{ShopifyConfig, ApiKey, ApiSecretKey};
// Create config
let config = ShopifyConfig::builder()
.api_key(ApiKey::new("your-key").unwrap())
.api_secret_key(ApiSecretKey::new("your-secret").unwrap())
.build()
.unwrap();
// Extract from HTTP request headers and body
let request = WebhookRequest::new(
body_bytes.to_vec(), // Raw request body
hmac_header.to_string(), // X-Shopify-Hmac-SHA256 header
Some(topic_header.to_string()), // X-Shopify-Topic header
Some(shop_header.to_string()), // X-Shopify-Shop-Domain header
Some(webhook_id.to_string()), // X-Shopify-Webhook-Id header
Some(api_version.to_string()), // X-Shopify-API-Version header
);
// Verify
match verify_webhook(&config, &request) {
Ok(context) => {
println!("Verified webhook from: {:?}", context.shop_domain());
// Process the webhook...
}
Err(e) => {
println!("Verification failed: {}", e);
// Reject the request
}
}
Low-Level Verification
For custom integrations:
use shopify_sdk::webhooks::verify_hmac;
let body = b"webhook payload";
let hmac_header = "base64-encoded-hmac";
let secret = "your-api-secret";
if verify_hmac(body, hmac_header, secret) {
println!("Valid signature");
} else {
println!("Invalid signature - reject request");
}
Webhook Headers
The SDK provides constants for standard Shopify webhook headers:
use shopify_sdk::webhooks::{
HEADER_HMAC, // X-Shopify-Hmac-SHA256
HEADER_TOPIC, // X-Shopify-Topic
HEADER_SHOP_DOMAIN, // X-Shopify-Shop-Domain
HEADER_WEBHOOK_ID, // X-Shopify-Webhook-Id
HEADER_API_VERSION, // X-Shopify-API-Version
};
Single Topic Registration
Register a single topic when needed:
let result = registry.register(&session, &config, WebhookTopic::OrdersCreate).await?;
if result.success {
println!("Successfully registered orders/create webhook");
}
Best Practices
-
Respond quickly - Return HTTP 200 within 5 seconds. Queue processing for later:
// In your HTTP handler: // 1. Verify the webhook // 2. Queue for processing // 3. Return 200 immediately match verify_webhook(&config, &request) { Ok(context) => { // Queue for async processing job_queue.enqueue(WebhookJob { context, payload: body.clone(), }); // Return 200 immediately HttpResponse::Ok() } Err(_) => HttpResponse::Unauthorized() } -
Implement idempotency - Use
X-Shopify-Webhook-Idto handle duplicates:let webhook_id = context.webhook_id(); if already_processed(webhook_id) { return Ok(()); // Skip duplicate } process_and_mark_completed(webhook_id); -
Handle retries gracefully - Shopify retries failed webhooks for 48 hours
-
Use key rotation - Configure
old_api_secret_keyduring secret rotation:let config = ShopifyConfig::builder() .api_key(ApiKey::new("key").unwrap()) .api_secret_key(ApiSecretKey::new("new-secret").unwrap()) .old_api_secret_key(ApiSecretKey::new("old-secret").unwrap()) .build() .unwrap(); -
Log webhook metadata - Include shop, topic, and ID for debugging
Error Handling
use shopify_sdk::webhooks::WebhookError;
fn handle_webhook_error(error: WebhookError) {
match error {
WebhookError::InvalidHmac => {
// Authentication failed - reject request
}
WebhookError::RegistrationNotFound { topic } => {
// Topic not configured locally
}
WebhookError::GraphqlError(e) => {
// API error during registration
}
WebhookError::ShopifyError { message } => {
// Shopify returned an error
}
WebhookError::NoHandlerForTopic { topic } => {
// No handler configured for topic
}
WebhookError::PayloadParseError { message } => {
// Failed to parse webhook body as JSON
}
_ => {
// Other errors
}
}
}
Next Steps
- GraphQL Admin API - Make API calls in response to webhooks
- Custom Apps - Webhook handling for custom apps