Partners API
Webhooks
Get customer changes pushed to your endpoint, verify signatures, and handle retries.
Topics
Five topics are available:
| Topic | Fires when | Payload |
|---|---|---|
customer_updated | A customer is created or updated. | The full customer object — the same shape returned by the customer endpoints. |
gifting_reward_updated | A creator submits gift details and product selections, or fulfillment details change. | The full merchant-managed gifting reward, including immutable product snapshots in selected_products. |
discount_code_created | A custom-commerce discount code is created. | The discount code object from the discount code endpoint. Requires discounts.read to subscribe. |
discount_code_updated | A custom-commerce discount code is updated. | The discount code object. Requires discounts.read to subscribe. |
discount_code_deleted | A custom-commerce discount code is deleted. | Snapshot of the discount code before deletion. Requires discounts.read to subscribe. |
An example delivery:
{
"id": 123345567,
"email": "enrolled@superfiliate.com",
"phone": "+12125551234",
"first_name": "Alice",
"last_name": "Wonder",
"instagram_handle": "alice",
"tiktok_handle": "alice_tiktok",
"balance": "10.0",
"code": "CODE-FOR-FRIENDS",
"reward_code": "CODE-TO-REDEEM-BALANCE",
"microsite_url": "https://shop.com/CODE-FOR-FRIENDS",
"redeem_url": "https://shop.com/discount/CODE-TO-REDEEM-BALANCE?utm_...",
"campaign_id": 7890,
"enrolled_at": "2024-01-01 12:00:00 UTC",
"tags": ["VIP Customer", "Newsletter Subscriber"]
}{
"id": 123456,
"gifting_stage": "ready_to_send",
"campaign": { "id": 345678, "name": "Summer gifting" },
"creator": {
"id": 789012,
"first_name": "Alex",
"last_name": "Creator",
"email": "alex@example.com",
"phone": "+15555550123"
},
"shipping_address": {
"address1": "123 Main St",
"city": "New York",
"country_code": "US",
"zip": "10001"
},
"selected_products": [
{
"product_external_id": "coffee-bundle",
"variant_external_id": "coffee-dark",
"product_title": "Coffee bundle",
"variant_title": "Dark roast",
"image_url": "https://example.com/dark-roast.jpg",
"sku": "DARK-ROAST",
"options": [{ "name": "Roast", "value": "Dark" }],
"quantity": 1,
"selected_at": "2026-08-11T18:00:00Z"
}
],
"fulfillment": null,
"details_submitted_at": "2026-08-11T18:00:00Z",
"created_at": "2026-08-10T18:00:00Z",
"updated_at": "2026-08-11T18:00:00Z"
}Delivery & retries
- Respond with a
2xxquickly — do the heavy lifting after you acknowledge. - Anything else is retried multiple times using a randomized exponential backoff algorithm, for roughly 20 days before we give up.
- Make your handler idempotent: deliveries can arrive more than once and out of order. Treat each payload as the latest known state of that resource, not as an event log.
Headers
| Header | Contents |
|---|---|
X-SUPERFILIATE-HMAC-SHA256 | Base64 HMAC signature of the raw request body, used for verification below. |
X-SUPERFILIATE-SHOP-DOMAIN | The store the event belongs to, eg. example.com — useful when one endpoint serves several stores. |
X-SHOPIFY-SHOP-DOMAIN | The Shopify domain for Shopify integrations, eg. example.myshopify.com. |
Verify signatures
Every delivery is signed with your client_secret. We compute an HMAC-SHA256 digest of the raw request body, Base64-encode it, then compare it with the signature header in constant time.
class WebhooksController < ApplicationController
before_action :verify_webhook_authenticity!
def index
shop_domain = request.headers["X-SUPERFILIATE-SHOP-DOMAIN"]
Rails.logger.info("Webhook received: #{shop_domain} #{request.raw_post}")
end
private
def verify_webhook_authenticity!
hmac_header = request.headers["X-SUPERFILIATE-HMAC-SHA256"]
calculated_hmac = Base64.strict_encode64(
OpenSSL::HMAC.digest("sha256", CLIENT_SECRET, request.raw_post)
)
return if ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
head :unauthorized
end
endManage subscriptions
Subscriptions are managed entirely through the API: