Agent skill

VTEX Webhooks Skill

Receive and authenticate VTEX Orders API hook webhooks. Use when setting up a VTEX order hook endpoint, checking the secret header you add in the hook configuration, answering the hookConfig ping, debugging 401s, or handling order status changes like order-created, payment-approved, ready-for-handling, invoiced and canceled.

Install this skill

npx skills add hookdeck/webhook-skills --skill vtex-webhooks


VTEX's Orders API Hook (part of Feed v3 and Hook) POSTs a small JSON notification to your URL every time an order changes status and matches your filter. Hooks are configured by API, one per appKey. Requests are not signed. VTEX does send whatever you put in the headers object of the hook configuration, so you add a secret header yourself and your endpoint checks it.

When to Use This Skill

  • How do I receive VTEX order webhooks?
  • How do I authenticate a VTEX hook if there is no signature?
  • How do I answer the {"hookConfig": "ping"} request VTEX sends when I save the hook?
  • How do I handle order-created, payment-approved, ready-for-handling, invoiced or canceled?
  • Why does my VTEX hook endpoint return 401, or why did VTEX stop sending notifications?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyVtexRequest(headers, name = process.env.VTEX_WEBHOOK_HEADER || 'X-Webhook-Secret',
                           secret = process.env.VTEX_WEBHOOK_SECRET) {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers[name.toLowerCase()];       // Node lower-cases header names
  return Boolean(received) && safeEqual(received, secret); // whole value, no Bearer parsing
}
  • You choose the header in the hook configuration, e.g. "headers": {"X-Webhook-Secret": "<secret>"}. The examples read its name from VTEX_WEBHOOK_HEADER (default X-Webhook-Secret) and the exact, whole value from VTEX_WEBHOOK_SECRET, including any Bearer prefix you typed. Look the header up case-insensitively and compare the whole value; do not strip or parse a prefix.
  • On setup VTEX POSTs {"hookConfig": "ping"} and needs a 200, or the hook can't be saved. Reply 200 to the ping before the auth check and do nothing with it (the docs don't say whether the ping carries your headers).
  • Return 401 on a missing or wrong header, 400 for a body that isn't a JSON object, 200 once handled (VTEX needs a 200 within 5000 ms).
  • Read the body raw (express.raw({ type: '*/*' })) and parse JSON only after the credential check.

A static shared credential authenticates the sender, not the body, and gives no replay protection (there is no signature and no timestamp). Use HTTPS only, and rotate the credential (re-run the hook configuration call and update VTEX_WEBHOOK_SECRET) if it leaks.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

The documented notification body is {Domain, OrderId, State, LastChange, Origin: {Account, Key}}. State is the order status (the API status names from VTEX's order flow table); dispatch on it.

StateMeaning
order-createdThe order was created
on-order-completedThe purchase was processed at checkout and the order data is complete
payment-pendingWaiting for payment
payment-approvedPayment approved
waiting-ffmt-authorizationWaiting for fulfillment authorization
ready-for-handlingReady to be handled by the seller
start-handlingHandling is about to start
handlingThe order is being handled
invoicedAn invoice was issued
cancelCancellation workflow status
canceledThe order was canceled

See VTEX's order flow documentation for the full status list; unknown states should still get a 200.

Environment Variables

VTEX_WEBHOOK_HEADER=X-Webhook-Secret      # header name you put in the hook "headers" object
VTEX_WEBHOOK_SECRET=your-secret-value     # exact whole header value

Local Development

VTEX can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 vtex --path /webhooks/vtex

Use the printed URL as hook.url in the VTEX hook configuration.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 4, 2026

View on GitHub →