spree-events-webhooks

Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.

17 stars

Best use case

spree-events-webhooks is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.

Teams using spree-events-webhooks should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/spree-events-webhooks/SKILL.md --create-dirs "https://raw.githubusercontent.com/OrcaQubits/agentic-commerce-skills-plugins/main/dist/antigravity/spree-commerce/.agent/skills/spree-events-webhooks/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/spree-events-webhooks/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How spree-events-webhooks Compares

Feature / Agentspree-events-webhooksStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Spree Events & Webhooks

## Before writing code

**Fetch live docs**:
1. Fetch https://spreecommerce.org/docs/developer/core-concepts/events for the event bus.
2. Fetch https://spreecommerce.org/docs/developer/core-concepts/webhooks for Webhooks 2.0 (HMAC, retries).
3. Inspect the live `lib/spree/event.rb` / `lib/spree/subscriber.rb` and `app/subscribers/` in the `spree` gem for the canonical event names per release.
4. Check the v5.4 announcement for any Webhooks 2.0 changes.
5. For verifying signatures, also check the `@spree/sdk` if you're consuming webhooks in TypeScript.

## Conceptual Architecture

### Why an Event Bus?

Spree's event bus (`Spree::Events`) replaces ad-hoc ActiveSupport::Notifications and `after_*` callbacks for cross-cutting concerns. Benefits:
- **Decoupled** — subscribers don't know about each other
- **Testable** — assert on event publication, not on side effects
- **Webhook-friendly** — Webhooks 2.0 piggybacks on the same events
- **Wildcard subscriptions** — `order.*`, `*.created`, `*` for cross-cutting logging

### Publishing Events

In core code:

```ruby
Spree::Bus.publish('order.completed', order: order, user: order.user)
```

Or via `publishes_lifecycle_events`:

```ruby
class Spree::Product < ApplicationRecord
  publishes_lifecycle_events  # auto-emits product.created/.updated/.deleted
end
```

### Subscribing

```ruby
# app/subscribers/order_completed_subscriber.rb
class OrderCompletedSubscriber < Spree::Subscriber
  subscribes_to 'order.completed'

  on 'order.completed', :handle_completed

  def handle_completed(event)
    order = event.order
    AccountingSync.enqueue(order_id: order.id)
  end
end
```

Subscribers in `app/subscribers/` auto-register on app boot. Otherwise:

```ruby
# config/initializers/spree.rb
Spree.subscribers << CustomSubscriber
```

### Wildcards

```ruby
subscribes_to 'order.*'   # all order events
subscribes_to '*.created' # all lifecycle creations
subscribes_to '*'         # everything (use for logging only)
```

### Canonical Event Catalog (verify against live source)

| Domain | Events |
|--------|--------|
| Order | `order.created`, `order.updated`, `order.completed`, `order.canceled`, `order.resumed`, `order.paid`, `order.shipped` |
| Payment | `payment.created`, `payment.updated`, `payment.paid` |
| Shipment | `shipment.created`, `shipment.updated`, `shipment.shipped`, `shipment.canceled`, `shipment.resumed` |
| Product | `product.activate`, `product.archive`, `product.out_of_stock`, `product.back_in_stock` |
| Lifecycle | `{model}.created`, `{model}.updated`, `{model}.deleted` for any model with `publishes_lifecycle_events` |
| Cart | `cart.add_item`, `cart.remove_item`, `cart.update` |
| User | `user.created`, `user.password_reset_requested` |

This list **isn't exhaustive** — releases add events. Always re-check.

### Event Payload Shape

An `Event` object exposes the payload keys as methods:

```ruby
on 'order.completed', :handle
def handle(event)
  event.order        # Spree::Order
  event.user         # Spree::User
  event.firing_class # Spree::Order (the publisher)
end
```

### Webhooks 2.0

Webhooks subscribe to Spree events and forward HMAC-signed POSTs to external URLs. Configured per Store in admin (Settings → Webhooks).

A webhook endpoint declares:
- **URL** — your receiver
- **Event subscriptions** — pick events (e.g., `order.completed`, `payment.paid`)
- **Secret** — used to sign payloads

### Delivery Mechanics

1. Spree event publishes
2. `WebhookEventSubscriber` matches active endpoints
3. For each match, enqueues a Sidekiq job
4. Worker POSTs to the endpoint URL with body `{ event, data, timestamp }`
5. Signs with `X-Spree-Webhook-Signature: sha256=<hex>` (HMAC-SHA256 of body using shared secret)
6. Expects 2xx; otherwise retries with exponential backoff up to 5 attempts
7. After 5 failures, marks the delivery dead-letter for manual replay

### Signature Verification (Consumer Side)

```ruby
def verify_signature(body, signature_header, secret)
  expected = OpenSSL::HMAC.hexdigest('SHA256', secret, body)
  ActiveSupport::SecurityUtils.secure_compare(expected, signature_header.sub(/^sha256=/, ''))
end
```

```typescript
import { createHmac, timingSafeEqual } from 'crypto';

function verify(body: string, header: string, secret: string) {
  const expected = createHmac('sha256', secret).update(body).digest('hex');
  const received = header.replace(/^sha256=/, '');
  return timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
```

### Idempotency on the Consumer

Spree retries on non-2xx. Make your handler idempotent — keyed by event ID or order ID + state.

## Implementation Guidance

### Designing a Subscriber

Pattern: one subscriber class per concern, not per event.

```ruby
class AnalyticsSubscriber < Spree::Subscriber
  subscribes_to 'order.completed', 'order.canceled', 'product.activate'

  on 'order.completed', :track_purchase
  on 'order.canceled',  :track_cancellation
  on 'product.activate', :track_launch

  private

  def track_purchase(event)
    Analytics.track(
      user_id: event.order.user_id,
      event: 'purchase',
      properties: { revenue: event.order.total }
    )
  end

  def track_cancellation(event)
    Analytics.track(user_id: event.order.user_id, event: 'cancellation')
  end

  def track_launch(event)
    Analytics.track(event: 'product_launched', properties: { id: event.product.id })
  end
end
```

### Async Subscribers

Don't block the request — enqueue Sidekiq jobs:

```ruby
on 'order.completed', :handle

def handle(event)
  EmailJob.perform_later(order_id: event.order.id)
end
```

Webhooks 2.0 are already async via Sidekiq — your custom subscriber doesn't need to re-async unless it's heavy.

### Publishing Custom Events

For extension code:

```ruby
# In a service object
Spree::Bus.publish('my_app.special_discount_applied', order: order, amount: amount)

# Subscribe
class MyAppSubscriber < Spree::Subscriber
  subscribes_to 'my_app.special_discount_applied'
  on 'my_app.special_discount_applied', :log_it
end
```

Use a `my_app.` prefix to avoid collisions with core events.

### Wiring Webhooks 2.0

In admin → Settings → Webhooks:
- URL: `https://your-app.com/webhooks/spree`
- Events: pick from the catalog
- Secret: generated; store in your consumer's env

Verify against the live admin UI — Webhooks 2.0 management may have moved.

### Receiving a Webhook (Rails)

```ruby
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def spree
    body = request.body.read
    unless verify_signature(body, request.headers['X-Spree-Webhook-Signature'], ENV['SPREE_WEBHOOK_SECRET'])
      head :unauthorized and return
    end

    payload = JSON.parse(body)
    case payload['event']
    when 'order.completed' then OrderCompletedHandler.perform_later(payload['data'])
    end

    head :ok
  end
end
```

Respond 2xx **immediately** — process async. Slow handlers hit the retry threshold.

### Receiving a Webhook (Next.js)

```typescript
// app/api/webhooks/spree/route.ts
export async function POST(req: Request) {
  const body = await req.text();
  const signature = req.headers.get('x-spree-webhook-signature') ?? '';
  if (!verify(body, signature, process.env.SPREE_WEBHOOK_SECRET!)) {
    return new Response('Unauthorized', { status: 401 });
  }
  const payload = JSON.parse(body);
  // Enqueue async — Inngest, Trigger.dev, BullMQ, etc.
  return new Response('OK');
}
```

### Common Pitfalls

- **Subscriber that doesn't enqueue async work** — slows down the request and stalls the bus.
- **Forgetting to register a subscriber** — non-`app/subscribers/` location requires `Spree.subscribers << ...`.
- **Hardcoding event names** — make a constant, since event names occasionally rename across releases.
- **Verifying signature against the wrong secret** — multi-store deployments have a secret per Webhook endpoint, not per Store.
- **Slow webhook receiver** — Spree retries; you get a duplicate-handling problem. Always 2xx fast.
- **Subscribing to `*` in production** — performance hazard. Use for dev/diagnostics only.
- **Assuming event payload shape** — verify the publisher's call site; lifecycle vs custom events have different keys.

Always cross-reference the live `app/subscribers/` directory and the published events in the `spree` gem source — the event taxonomy evolves with new features.

Related Skills

ucp-orders-webhooks

17
from OrcaQubits/agentic-commerce-skills-plugins

Implement UCP Order capability and webhook delivery — post-purchase order management with fulfillment tracking, adjustments (refunds/returns), and cryptographically signed webhook notifications. Use when building order management or webhook infrastructure.

spree-upgrades

17
from OrcaQubits/agentic-commerce-skills-plugins

Upgrade a Spree application — version-to-version migration paths (v4 → v5 is a major rewrite; v5.x → v5.y are simpler), Gemfile pinning strategy, decorator brittleness across versions, deprecated gems to remove (`spree_auth_devise`, `deface`, `spree_backend`), API v2 → v3 migration via `spree_legacy_api_v2`, admin Bootstrap → Tailwind transition, the upgrade guide workflow, and rollback strategy. Use when planning a Spree upgrade or auditing technical debt blocking one.

spree-typescript-sdk

17
from OrcaQubits/agentic-commerce-skills-plugins

Build storefronts and integrations using `@spree/sdk` — the official TypeScript SDK for Spree's API v3 (v5.4+). Covers installation, the resource-builder pattern (`spree.products.list`, `spree.checkout.create`, etc.), Zod schema validation, server-only auth (httpOnly JWTs, never exposing API keys), error handling, typing customizations, and pinning SDK versions to backend Spree releases. Use when integrating Spree into a Next.js / Node.js / TypeScript project.

spree-testing

17
from OrcaQubits/agentic-commerce-skills-plugins

Test Spree applications and extensions with RSpec — `spree_dev_tools` gem (v5.2+) for factories and helpers, FactoryBot patterns (prefer `build` over `create`), Capybara feature specs, controller/request specs, testing decorators and subscribers, dummy app for extension testing, system specs for the Hotwire admin, and CI patterns. Use when writing Spree tests, setting up CI, or refactoring slow specs.

spree-shipping-fulfillment

17
from OrcaQubits/agentic-commerce-skills-plugins

Build and customize Spree's shipping and fulfillment — ShippingMethod, ShippingCategory, Zone/ZoneMember, ShippingRate, the Stock::Estimator service, StockLocation/StockItem/StockMovement, multi-shipment orders, ShippingCalculator classes (FlatRate, FlatPercentItemTotal, PerItem, FlexiRate), shipment state machine, returns (ReturnAuthorization → CustomerReturn → Reimbursement → Refund), and integrating carrier APIs (UPS, FedEx, ShipStation). Use when configuring shipping rules, building fulfillment integrations, or debugging shipping-rate calculations.

spree-setup

17
from OrcaQubits/agentic-commerce-skills-plugins

Bootstrap a new Spree project — `create-spree-app` CLI (v5.2+), `spree-starter` Rails backend, the Next.js storefront repo, `bin/rails g spree:install`, sample data, Docker Compose, and the PostgreSQL + Redis + Sidekiq prerequisites. Use when starting a new Spree project from scratch or onboarding an existing repo.

spree-security

17
from OrcaQubits/agentic-commerce-skills-plugins

Secure a Spree deployment — Rails credentials and env-var hygiene, Devise auth (Spree v5 ships it in-core; `spree_auth_devise` is archived), CanCanCan authorization rules, Doorkeeper OAuth2 scopes, Storefront publishable key vs admin API key, webhook HMAC verification, OWASP Top 10 for Rails (mass assignment, CSRF, SQL injection via Ransack, XSS, IDOR through prefixed IDs), PCI scope (Spree never touches raw cards thanks to gateway tokenization), and multi-store data isolation. Use when auditing a Spree app, hardening a deploy, or addressing a security incident.

spree-promotions

17
from OrcaQubits/agentic-commerce-skills-plugins

Build and customize Spree's promotions engine — Promotion + PromotionRule + PromotionAction + CouponCode + Adjustment, the bundled rules (FirstOrder/ItemTotal/Product/Taxon/User/OneUsePerUser/Country/CustomerGroup/etc.), bundled actions (CreateAdjustment/CreateItemAdjustments/FreeShipping/CreateLineItems), Calculator classes, coupon batches with CSV export, the v5.1+ advanced rule-based engine, and authoring custom rules/actions/calculators. Use when modeling promotions, building discount UIs, or extending the promotions engine.

spree-performance

17
from OrcaQubits/agentic-commerce-skills-plugins

Profile and optimize a Spree application — N+1 queries with bullet/scout, database indexing strategy for Spree's polymorphic associations, Rails fragment + Russian doll caching, ActiveStorage variant pre-generation, Sidekiq queue tuning, MeiliSearch vs Postgres FTS tradeoffs, Puma worker/thread sizing, CDN strategy for catalog pages, asset precompile time, and load testing. Use when Spree is slow, the database is hot, or you're preparing for a traffic spike (Black Friday, launch).

spree-payments

17
from OrcaQubits/agentic-commerce-skills-plugins

Integrate payment gateways with Spree — PaymentMethod model, the v5.4+ PaymentSession provider-agnostic checkout flow, Stripe via `spree_stripe` (Apple/Google Pay, Link, Connect for marketplaces), Adyen via `spree_adyen`, PayPal via `spree_paypal_checkout`, StoreCredit / GiftCard as payment methods, refunds, payment state machine, and authoring a custom gateway. Use when wiring a payment integration, handling webhooks from a gateway, or debugging payment-state issues.

spree-multi-store

17
from OrcaQubits/agentic-commerce-skills-plugins

Configure Spree for multi-store and multi-region commerce — one Rails install running many `Store` records, the v5.4+ `Market` model (currency + locale + payment methods + shipping per region), what's shared vs per-store (products+inventory+customers shared; orders+payments+themes per-store), the Marketplace module (Enterprise — vendors/commission/payouts via Stripe Connect), and the Multi-tenant SaaS model. Use when planning a multi-brand or multi-region Spree deployment.

spree-legacy-api-v2

17
from OrcaQubits/agentic-commerce-skills-plugins

Work with Spree's legacy v2 APIs — JSON:API-style Storefront API at `/api/v2/storefront/*` and Platform API at `/api/v2/platform/*`, Doorkeeper OAuth2 (password grant for storefront, client_credentials + admin scope for platform), the `spree_legacy_api_v2` gem (required in v5+ for backward compatibility), and migration patterns to API v3. Use when maintaining a v2 client during a migration window, integrating an older partner system, or deciding when to cut over to v3.