Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/eighty-poems-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@shopify/shopify-api': minor
'@shopify/shopify-app-react-router': minor
'@shopify/shopify-app-remix': minor
---

Stop reading the `shopify-event-id` and `shopify-resource-id` headers on events webhooks

Shopify is dropping these two headers from events deliveries, so the libraries no longer read them.

**`@shopify/shopify-api`:** `shopify-event-id` was a required header, so `shopify.webhooks.validate()` no longer fails with `MissingHeaders` when it is absent. `eventId` and `resourceId` are gone from the events validation result — use `webhookId` as the idempotency key.

```typescript
const check = await shopify.webhooks.validate({rawBody, rawRequest: request});
if (check.valid && check.webhookType === 'events') {
console.log(check.webhookId); // use this to deduplicate
}
```

**`@shopify/shopify-app-react-router` and `@shopify/shopify-app-remix`:** `resourceId` is gone from the webhook context, and `eventId` is now set only for webhooks, not events webhooks.

If you read `eventId` or `resourceId` off an events delivery in TypeScript, you will see a compile error. Both are on their way to being `undefined` at runtime regardless, because Shopify is no longer sending the headers.

Webhooks are unaffected. They keep the `X-Shopify-Event-Id` header and the `eventId` field.
2 changes: 0 additions & 2 deletions packages/apps/shopify-api/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ export const ShopifyEventsHeader = {
Topic: 'shopify-topic',
Domain: 'shopify-shop-domain',
ApiVersion: 'shopify-api-version',
EventId: 'shopify-event-id',
WebhookId: 'shopify-webhook-id',
Handle: 'shopify-handle',
Action: 'shopify-action',
ResourceId: 'shopify-resource-id',
TriggeredAt: 'shopify-triggered-at',
} as const;

Expand Down
4 changes: 0 additions & 4 deletions packages/apps/shopify-api/lib/webhooks/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export function headers({
// Events-specific fields
handle = '',
action = '',
resourceId = '',
triggeredAt = '',
eventId = '',
// Webhooks specific
Expand All @@ -48,7 +47,6 @@ export function headers({
webhookType?: WebhookTypeValue;
handle?: string;
action?: string;
resourceId?: string;
triggeredAt?: string;
eventId?: string;
name?: string;
Expand All @@ -67,10 +65,8 @@ export function headers({
[eventsHeaders.hmac]: hmac,
[eventsHeaders.topic]: topic,
...(webhookId && {[eventsHeaders.webhookId]: webhookId}),
...(eventId && {[eventsHeaders.eventId]: eventId}),
...(handle && {[eventsHeaders.handle]: handle}),
...(action && {[eventsHeaders.action]: action}),
...(resourceId && {[eventsHeaders.resourceId]: resourceId}),
...(triggeredAt && {[eventsHeaders.triggeredAt]: triggeredAt}),
};
}
Expand Down
12 changes: 0 additions & 12 deletions packages/apps/shopify-api/lib/webhooks/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,8 @@ describe('shopify.webhooks.validate', () => {
hmac: hmac(shopify.config.apiSecretKey, rawBody),
webhookType: 'events',
topic: 'Product',
eventId: 'event-abc',
action: 'create',
handle: 'my-webhook',
resourceId: 'gid://shopify/Product/123',
}),
)
.send(rawBody)
Expand All @@ -169,7 +167,6 @@ describe('shopify.webhooks.validate', () => {
webhookId: '123456789',
action: 'create',
handle: 'my-webhook',
resourceId: 'gid://shopify/Product/123',
});
});

Expand Down Expand Up @@ -201,9 +198,7 @@ describe('shopify.webhooks.validate', () => {
webhookType: 'events',
handle: 'test-handle',
action: 'update',
resourceId: 'gid://shopify/Product/456',
triggeredAt: '2026-01-27T12:00:00Z',
eventId: 'event-123',
}),
)
.send(rawBody)
Expand All @@ -214,9 +209,7 @@ describe('shopify.webhooks.validate', () => {
webhookId: '123456789',
handle: 'test-handle',
action: 'update',
resourceId: 'gid://shopify/Product/456',
triggeredAt: '2026-01-27T12:00:00Z',
eventId: 'event-123',
});
});

Expand Down Expand Up @@ -248,7 +241,6 @@ describe('shopify.webhooks.validate', () => {
{headers: {domain: ''}, missingHeader: 'shopify-shop-domain'},
{headers: {topic: ''}, missingHeader: 'shopify-topic'},
{headers: {webhookId: ''}, missingHeader: 'shopify-webhook-id'},
{headers: {eventId: ''}, missingHeader: 'shopify-event-id'},
])(
`returns false on missing events header $missingHeader`,
async (config) => {
Expand All @@ -258,7 +250,6 @@ describe('shopify.webhooks.validate', () => {
const requestHeaders = headers({
hmac: hmac(shopify.config.apiSecretKey, rawBody),
webhookType: 'events',
eventId: 'event-123',
...config.headers,
});

Expand Down Expand Up @@ -337,10 +328,8 @@ describe('shopify.webhooks.validate', () => {
hmac: hmac(shopify.config.apiSecretKey, rawBody),
webhookType: 'events',
topic: 'Product',
eventId: 'event-abc',
handle: 'my_first_subscription',
action: 'update',
resourceId: 'gid://shopify/Product/123',
}),
)
.send(rawBody)
Expand All @@ -352,7 +341,6 @@ describe('shopify.webhooks.validate', () => {
topic: 'PRODUCT',
handle: 'my_first_subscription',
action: 'update',
resourceId: 'gid://shopify/Product/123',
});
});
});
Expand Down
4 changes: 0 additions & 4 deletions packages/apps/shopify-api/lib/webhooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ export const WEBHOOK_HEADER_NAMES = {
domain: ShopifyEventsHeader.Domain,
apiVersion: ShopifyEventsHeader.ApiVersion,
webhookId: ShopifyEventsHeader.WebhookId,
eventId: ShopifyEventsHeader.EventId,
handle: ShopifyEventsHeader.Handle,
action: ShopifyEventsHeader.Action,
resourceId: ShopifyEventsHeader.ResourceId,
triggeredAt: ShopifyEventsHeader.TriggeredAt,
},
} as const;
Expand Down Expand Up @@ -187,10 +185,8 @@ export interface WebhooksWebhookFields extends BaseWebhookFields {
export interface EventsWebhookFields extends BaseWebhookFields {
webhookType: typeof WebhookType.Events;
webhookId: string;
eventId: string;
handle?: string;
action?: string;
resourceId?: string;
}

export type WebhookFields = WebhooksWebhookFields | EventsWebhookFields;
Expand Down
9 changes: 0 additions & 9 deletions packages/apps/shopify-api/lib/webhooks/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,6 @@ function checkEventsHeaders(
headerNames.webhookId,
missingHeaders,
);
const eventId = getRequiredHeader(
headers,
headerNames.eventId,
missingHeaders,
);

if (missingHeaders.length) {
return {
Expand All @@ -185,7 +180,6 @@ function checkEventsHeaders(
domain: domain!,
apiVersion: apiVersion!,
webhookId: webhookId!,
eventId: eventId!,
};

const handle = getHeader(headers, headerNames.handle);
Expand All @@ -194,9 +188,6 @@ function checkEventsHeaders(
const action = getHeader(headers, headerNames.action);
if (action) fields.action = action;

const resourceId = getHeader(headers, headerNames.resourceId);
if (resourceId) fields.resourceId = resourceId;

const triggeredAt = getHeader(headers, headerNames.triggeredAt);
if (triggeredAt) fields.triggeredAt = triggeredAt;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,8 @@ describe('Webhook validation', () => {
expect(result.shop).toBe(TEST_SHOP);
expect(result.topic).toBe('PRODUCT');
expect(result.webhookId).toBe('webhook-456');
expect(result.eventId).toBe('evt-123');
expect(result.handle).toBe('my-handle');
expect(result.action).toBe('update');
expect(result.resourceId).toBe('gid://shopify/Product/123');
expect(result.triggeredAt).toBe('2026-01-27T12:00:00Z');
expect(result.payload).toEqual(body);

Expand Down Expand Up @@ -312,10 +310,8 @@ function eventsWebhookHeaders(
'shopify-api-version': '2023-01',
'shopify-hmac-sha256': hmac,
'shopify-webhook-id': 'webhook-456',
'shopify-event-id': 'evt-123',
'shopify-handle': 'my-handle',
'shopify-action': 'update',
'shopify-resource-id': 'gid://shopify/Product/123',
'shopify-triggered-at': '2026-01-27T12:00:00Z',
...overrides,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ export function authenticateWebhookFactory<Topics extends string>(
webhookType: check.webhookType,
handle: check.handle,
action: check.action,
resourceId: check.resourceId,
triggeredAt: check.triggeredAt,
eventId: check.eventId,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,13 @@ interface Context<Topics = string | number | symbol> {
*/
action?: string;

/**
* The GID of the resource that triggered the webhook. Only available for events webhooks.
*/
resourceId?: string;

/**
* The timestamp when the webhook was triggered.
*/
triggeredAt?: string;

/**
* The unique event identifier.
* The unique event identifier. Only available for webhooks, not events webhooks.
*/
eventId?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,8 @@ describe('Webhook validation', () => {
expect(result.shop).toBe(TEST_SHOP);
expect(result.topic).toBe('PRODUCT');
expect(result.webhookId).toBe('webhook-456');
expect(result.eventId).toBe('evt-123');
expect(result.handle).toBe('my-handle');
expect(result.action).toBe('update');
expect(result.resourceId).toBe('gid://shopify/Product/123');
expect(result.triggeredAt).toBe('2026-01-27T12:00:00Z');
expect(result.payload).toEqual(body);

Expand Down Expand Up @@ -312,10 +310,8 @@ function eventsWebhookHeaders(
'shopify-api-version': '2023-01',
'shopify-hmac-sha256': hmac,
'shopify-webhook-id': 'webhook-456',
'shopify-event-id': 'evt-123',
'shopify-handle': 'my-handle',
'shopify-action': 'update',
'shopify-resource-id': 'gid://shopify/Product/123',
'shopify-triggered-at': '2026-01-27T12:00:00Z',
...overrides,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ export function authenticateWebhookFactory<Topics extends string>(
webhookType: check.webhookType,
handle: check.handle,
action: check.action,
resourceId: check.resourceId,
triggeredAt: check.triggeredAt,
eventId: check.eventId,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,13 @@ interface Context<Topics = string | number | symbol> {
*/
action?: string;

/**
* The GID of the resource that triggered the webhook. Only available for events webhooks.
*/
resourceId?: string;

/**
* The timestamp when the webhook was triggered.
*/
triggeredAt?: string;

/**
* The unique event identifier.
* The unique event identifier. Only available for webhooks, not events webhooks.
*/
eventId?: string;
}
Expand Down
Loading