1"use strict";(globalThis.webpackChunkdocumentation=globalThis.webpackChunkdocumentation||[]).push([[9915],{5861(e,r,n){n.r(r),n.d(r,{assets:()=>S,contentTitle:()=>A,default:()=>k,frontMatter:()=>C,metadata:()=>o,toc:()=>D});const o=JSON.parse('{"id":"guides/integrations/woocommerce","title":"WooCommerce e-invoicing: Factur-X and ZUGFeRD","description":"Generate EU-compliant e-invoices from WooCommerce orders by mapping the Order updated webhook payload to the PolyDoc API and receiving a Factur-X / ZUGFeRD PDF/A-3 hybrid.","source":"@site/docs/guides/integrations/woocommerce.md","sourceDirName":"guides/integrations","slug":"/guides/integrations/woocommerce","permalink":"/guides/integrations/woocommerce","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":26,"frontMatter":{"sidebar_position":26,"toc_min_heading_level":2,"toc_max_heading_level":3,"description":"Generate EU-compliant e-invoices from WooCommerce orders by mapping the Order updated webhook payload to the PolyDoc API and receiving a Factur-X / ZUGFeRD PDF/A-3 hybrid.","title":"WooCommerce e-invoicing: Factur-X and ZUGFeRD","sidebar_label":"WooCommerce"},"sidebar":"tutorialSidebar","previous":{"title":"UiPath","permalink":"/guides/integrations/uipath"},"next":{"title":"Zapier","permalink":"/guides/integrations/zapier"}}');var t=n(4848),s=n(8453),i=(n(6540),n(6784)),d=n(6188),c=n(3422),a=n(9728),l=n(4176),h=n(2362),m=n(8419),x=n(1811);const u=({icon:e,children:r})=>(0,t.jsxs)("div",{className:"flex flex-col sm:flex-row items-center sm:items-start",children:[(0,t.jsx)("div",{className:"mt-[0.9rem] me-3 shrink-0",children:(0,t.jsxs)("span",{className:"fa-layers fa-fw fa-2xl",children:[(0,t.jsx)(i.g,{icon:d.GEE,className:"text-yellow-300/50"}),(0,t.jsx)(i.g,{icon:e,transform:"shrink-10",className:"text-amber-500"})]})}),(0,t.jsx)(c.A,{className:"w-full min-w-0",children:r})]}),p=()=>(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("p",{children:["Turn a completed WooCommerce order into a compliant e-invoice: a single PDF/A-3 that carries the human-readable invoice and the embedded Factur-X / ZUGFeRD XML, then deliver it to your customer.",(0,t.jsx)("br",{}),(0,t.jsx)("code",{children:"WooCommerce (order updated) \u2192 your handler \u2192 PolyDoc API \u2192 email / order Note"})]}),(0,t.jsx)(l.A,{className:"mt-3",children:(0,t.jsx)("span",{children:"WooCommerce core sends a basic order email and a printable order page but has no native ZUGFeRD, XRechnung, Factur-X, or Peppol support: EN 16931 compliance only comes via third-party plugins. PolyDoc adds exactly that: pass the order data and receive a verified PDF/A-3b hybrid with embedded CII XML."})}),(0,t.jsx)(a.A,{className:"mt-3",children:"This guide is orchestrator-agnostic. The steps work in a raw backend handler, or inside Make / Zapier / n8n / Pipedream. Anywhere you can receive a webhook and make an HTTP request, the flow is the same."})]}),j=()=>(0,t.jsxs)(u,{icon:d.vkd,children:[(0,t.jsxs)("p",{className:"mb-1",children:["In wp-admin, go to ",(0,t.jsx)("strong",{children:"WooCommerce \u2192 Settings \u2192 Advanced \u2192 Webhooks \u2192 Add webhook"}),". Set:"]}),(0,t.jsxs)("ul",{className:"mb-2 list-outside list-disc ps-5 marker:text-base-content",children:[(0,t.jsxs)("li",{children:[(0,t.jsx)("strong",{children:"Topic"}),": ",(0,t.jsx)("code",{children:"Order updated"})]}),(0,t.jsxs)("li",{children:[(0,t.jsx)("strong",{children:"Delivery URL"}),": your handler URL (must be HTTPS)"]}),(0,t.jsxs)("li",{children:[(0,t.jsx)("strong",{children:"Secret"}),": a long random string (or let WooCommerce generate one). You need this in Step 2 to verify the HMAC."]}),(0,t.jsxs)("li",{children:[(0,t.jsx)("strong",{children:"API Version"}),": WP REST API Integration v3"]}),(0,t.jsxs)("li",{children:[(0,t.jsx)("strong",{children:"Status"}),": Active"]})]}),(0,t.jsx)(m.A,{className:"my-3",title:"wp-admin",children:(0,t.jsx)(x.A,{src:"/img/guides/woocommerce/woocommerce-use-case-1-step1-webhook.webp",alt:"WooCommerce wp-admin: Settings \u2192 Advanced \u2192 Webhooks \u2192 Add webhook, with topic 'Order updated', Delivery URL, and a Secret field for the HMAC.",width:1622,height:1064})}),(0,t.jsxs)(a.A,{className:"mt-3",children:["The Order updated topic fires on every order change (status moves, edits, refunds). Step 2 gates on ",(0,t.jsx)("code",{children:'status === "completed"'}),"; swap to"," ",(0,t.jsx)("code",{children:'"processing"'})," if you invoice on payment instead of on fulfilment. For the bulk archive in Use Case 2, also create a"," ",(0,t.jsx)("strong",{children:"Consumer Key + Consumer Secret"})," under"," ",(0,t.jsx)("strong",{children:"Settings \u2192 Advanced \u2192 REST API \u2192 Add key"})," (Read permissions are enough)."]})]}),g=()=>(0,t.jsxs)(u,{icon:d.oI1,children:[(0,t.jsxs)("p",{className:"mb-1",children:["Your handler receives the POST. Before trusting it, verify the"," ",(0,t.jsx)("code",{children:"X-WC-Webhook-Signature"})," header: compute an HMAC-SHA256 of the"," ",(0,t.jsx)("strong",{children:"raw request body"})," with your webhook secret, base64-encode it, and compare in constant time. Then guard on the order status so you only invoice completed orders:"]}),(0,t.jsx)(h.A,{language:"javascript",className:"my-3",children:'import
1crypto from "node:crypto";\n\n// rawBody MUST be the raw request bytes, not the parsed JSON.\nfunction verifyWooCommerceWebhook(rawBody, signatureHeader) {\n const digest = crypto\n .createHmac("sha256", process.env.WC_WEBHOOK_SECRET)\n .update(rawBody, "utf8")\n .digest("base64");\n return crypto.timingSafeEqual(\n Buffer.from(digest),\n Buffer.from(signatureHeader),\n );\n}\n\nif (!verifyWooCommerceWebhook(rawBody, request.headers["x-wc-webhook-signature"])) {\n return new Response("invalid signature", { status: 401 });\n}\n\nconst order = JSON.parse(rawBody);\nif (order.status !== "completed") return; // or "processing" if you invoice on payment'}),(0,t.jsx)(l.A,{className:"mt-3",children:(0,t.jsxs)("span",{children:["HMAC verification is not optional. Without it, anyone who learns your endpoint URL can POST a forged order and make you generate (and email) invoices for data they control. Verify on the ",(0,t.jsx)("strong",{children:"raw body"})," ","before any JSON parsing reorders fields."]})})]}),y=()=>(0,t.jsxs)(u,{icon:d.JGI,children:[(0,t.jsxs)("p",{className:"mb-1",children:["WooCommerce webhook v3 delivers the ",(0,t.jsx)("strong",{children:"full order object"}),", so there is no second fetch in Use Case 1. The fields the next step maps from:"]}),(0,t.jsxs)(a.A,{className:"mt-3",children:[(0,t.jsx)("code",{children:"number"})," (merchant-facing order number, configurable),"," ",(0,t.jsx)("code",{children:"date_completed"})," / ",(0,t.jsx)("code",{children:"date_created"}),","," ",(0,t.jsx)("code",{children:"currency"}),", ",(0,t.jsx)("code",{children:"billing.{first_name,last_name,company,email}"}),","," ",(0,t.jsx)("code",{children:"billing.{address_1,address_2,city,postcode,country,state}"}),","," ",(0,t.jsx)("code",{children:"line_items[]"})," (each with ",(0,t.jsx)("code",{children:"name"}),","," ",(0,t.jsx)("code",{children:"quantity"}),", ",(0,t.jsx)("code",{children:"price"}),", ",(0,t.jsx)("code",{children:"subtotal"}),","," ",(0,t.jsx)("code",{children:"total"}),", ",(0,t.jsx)("code",{children:"total_tax"}),", ",(0,t.jsx)("code",{children:"taxes[]"}),"), and the order totals ",(0,t.jsx)("code",{children:"subtotal"})," / ",(0,t.jsx)("code",{children:"total_tax"})," /"," ",(0,t.jsx)("code",{children:"total"}),". The flag ",(0,t.jsx)("code",{children:"prices_include_tax"})," tells you whether ",(0,t.jsx)("code",{children:"line.price"})," is gross or net: Step 4 handles both."]})]}),v=()=>(0,t.jsxs)(u,{icon:d.C4u,children:[(0,t.jsxs)("p",{className:"mb-1",children:["Map the order to PolyDoc's ",(0,t.jsx)("code",{children:"eInvoice"})," block. The"," ",(0,t.jsx)("code",{children:"seller"})," is your own shop (your legal entity: set it once). Everything else comes from the order:"]}),(0,t.jsx)("div",{className:"overflow-x-auto my-3",children:(0,t.jsxs)("table",{children:[(0,t.jsx)("thead",{children:(0,t.jsxs)("tr",{children:[(0,t.jsxs)("th",{children:["PolyDoc ",(0,t.jsx)("code",{children:"eInvoice.invoice"})]}),(0,t.jsx)("th",{children:"WooCommerce source"}),(0,t.jsx)("th",{children:"Transform"})]})}),(0,t.jsxs)("tbody",{children:[(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"number"})}),(0,t.jsxs)("td",{children:["order ",(0,t.jsx)("code",{children:"number"})]}),(0,t.jsx)("td",{children:"direct (string; merchant-facing)"})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"issueDate"})}),(0,t.jsxs)("td",{children:[(0,t.jsx)("code",{children:"date_completed"})," (else ",(0,t.jsx)("code",{children:"date_created"}),")"]}),(0,t.jsxs)("td",{children:[(0,t.jsx)("code",{children:".slice(0,10)"})," (ISO 8601)"]})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"currencyCode"})}),(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"currency"})}),(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:".toUpperCase()"})})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"buyer.name"})}),(0,t.jsxs)("td",{children:[(0,t.jsx)("code",{children:"billing.company"})," else ",(0,t.jsx)("code",{children:"first_name + last_name"})]}),(0,t.jsx)("td",{children:"prefer company for B2B"})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"buyer.email"})}),(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"billing.email"})}),(0,t.jsx)("td",{children:"direct"})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"buyer.address"})}),(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"billing.{address_1,city,postcode,country}"})}),(0,t.jsxs)("td",{children:[(0,t.jsx)("code",{children:"postcode\u2192postalCode"}),", ",(0,t.jsx)("code",{children:"country\u2192countryCode"})," (already ISO-2)"]})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"lines[]"})}),(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"line_items[]"})}),(0,t.jsxs)("td",{children:[(0,t.jsx)("code",{children:".map()"})," (below)"]})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"lines[].unitPrice"})}),(0,t.jsxs)("td",{children:["line ",(0,t.jsx)("code",{children:"price"})]}),(0,t.jsxs)("td",{children:[(0,t.jsx)("code",{children:"parseFloat"}),"; if ",(0,t.jsx)("code",{children:"prices_include_tax"}),", divide out tax"]})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"lines[].lineTotal"})}),(0,t.jsxs)("td",{children:["line ",(0,t.jsx)("code",{children:"subtotal"})," (pre-discount) or ",(0,t.jsx)("code",{children:"total"})]}),(0,t.jsxs)("td",{children:[(0,t.jsx)("code",{children:"parseFloat"}),", tax-adjusted"]})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"lines[].vatRate"})}),(0,t.jsxs)("td",{children:["from ",(0,t.jsx)("code",{children:"line.taxes[]"})," + ",(0,t.jsx)("code",{children:"order.tax_lines[]"}),", else ",(0,t.jsx)("code",{children:"total_tax / subtotal x 100"})]}),(0,t.jsx)("td",{children:"direct percent"})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"totalNet / Tax / Gross"})}),(0,t.jsxs)("td",{children:["order ",(0,t.jsx)("code",{children:"subtotal / total_tax / total"})]}),(0,t.jsx)("td",{children:(0,t.jsx)("code",{children:"parseFloat"})})]})]})]})}),(0,t.jsxs)(a.A,{className:"mt-3",children:[(0,t.jsx)("strong",{children:"prices_include_tax"}),": if ",(0,t.jsx)("code",{children:"true"}),","," ",(0,t.jsx)("code",{children:"line.price"})," is gross, so the net unit price is"," ",(0,t.jsx)("code",{children:"price / (1 + rate/100)"}),"; if ",(0,t.jsx)("code",{children:"false"}),", the price is already net. ",(0,t.jsx)("strong",{children:"Trigger status"}),": most stores invoice on"," ",(0,t.jsx)("code",{children:"completed"}),"; some on ",(0,t.jsx)("code",{children:"processing"})," (paid but pre-shipping). Reject any status that isn't your chosen one."," ",(0,t.jsx)("strong",{children:"Multiple tax rates per line"}),": ",(0,t.jsx)("code",{children:"line.taxes[]"})," ","can carry more than one rate. Pick the dominant rate, or split the line into multiple PolyDoc lines. ",(0,t.jsx)("strong",{children:"VAT category"}),": use"," ",(0,t.jsx)("code",{children:'"S"'})," for standard-rated lines; ",(0,t.jsx)("code",{children:'"Z"'})," zero,"," ",(0,t.jsx)("code",{children:'"E"'})," exempt, ",(0,t.jsx)("code",{children:'"AE"'})," reverse-charge,"," ",(0,t.jsx)("code",{children:'"K"'})," intra-EU, ",(0,t.jsx)("code",{children:'"G"'})," export for the others."]}),(0,t.jsxs)("p",{className:"mb-1 mt-3",children:["Then POST to PolyDoc. ",(0,t.jsx)("code",{children:"source"})," renders the visual invoice from your template; ",(0,t.jsx)("code",{children:"eInvoice"})," generates and embeds the CII XML;"," ",(0,t.jsx)("code",{children:"verify: true"})," runs VeraPDF and fails the request if the output isn't PDF/A-3b compliant:"]}),(0,t.jsx)(h.A,{language:"javascript",className:"my-3",children:'const order = JSON.parse(rawBody); // the verified order from Step 2\nconst inclusive = order.prices_include_tax === true;\n\n// Order-level rate from tax_lines (most stores have one); fall back to a compute.\nconst orderRate =\n parseFloat(order.tax_lines?.[0]?.rate_percent ?? 0) ||\n (parseFloat(order.total_tax) / parseFloat(order.subtotal)) * 100 ||\n 0;\n\nconst lines = order.line_items.map((l) => {\n const lineRate =\n parseFloat(l.taxes?.[0]?.rate_id && order.tax_lines?.find((t) => t.rate_id === l.taxes[0].rate_id)?.rate_percent) ||\n orderRate;\n const listed = parseFloat(l.price);\n const unitPrice = inclusive ? listed / (1 + lineRate / 100) : listed;\n return {\n description: l.name,\n quantity: l.quantity,\n unitCode: "C62", // "HUR" for hours, "DAY" for days, etc.\n unitPrice,\n lineTotal: unitPrice * l.quantity,\n vatRate: lineRate,\n vatCategoryCode: lineRate > 0 ? "S" : "Z",\n };\n});\n\nconst issueDate = (order.date_completed ?? order.date_created).slice(0, 10);\nconst dueDate = order.date_paid\n ? order.date_paid.slice(0, 10)\n : new Date(new Date(issueDate).getTime() + 30 * 864e5).toISOString().slice(0, 10);\n\nconst body = {\n source: "[template:YOUR_TEMPLATE_ID]",\n templateData: {\n invoice_number: order.number,\n invoice_date: issueDate,\n invoice_total: parseFloat(order.total),\n customer_name: order.billing.company || `${order.billing.first_name} ${order.billing.last_name}`,\n customer_email: order.billing.email,\n items: order.line_items.map((l) => ({\n quantity: l.quantity,\n name: l.name,\n description: l.name,\n price: parseFloat(l.price),\n })),\n },\n eInvoice: {\n standard: "facturx", // or "zugferd"\n profile: "en16931",\n verify: true,\n invoice: {\n number: String(order.number),\n issueDate,\n dueDate,\n paymentTerms: "Net 30 days", // BT-9/BT-20: see note\n currencyCode: order.currency.toUpperCase(),\n seller: YOUR_BUSINESS, // { name, address, taxId }: your own, set once\n buyer: {\n name: order.billing.company || `${order.billing.first_name} ${order.billing.last_name}`,\n email: order.billing.email,\n address: {\n line1: order.billing.address_1,\n line2: order.billing.address_2 || undefined,\n city: order.billing.city,\n postalCode: order.billing.postcode,\n countryCode: order.billing.country, // ISO-2 already\n },\n },\n lines,\n totalNetAmount: parseFloat(order.subtotal),\n totalTaxAmount: parseFloat(order.total_tax),\n totalGrossAmount: parseFloat(order.total),\n },\n },\n};\n\nconst res = await fetch("https://api.polydoc.tech/pdf/convert", {\n method: "POST",\n headers: {\n Authorization: "Bearer YOUR_API_KEY",\n "Content-Type": "application/json",\n "X-Sandbox": "true", // drop in production\n },\n body: JSON.stringify(body),\n});\nconst pdf = Buffer.from(await res.arrayBuffer()); // the PDF/A-3 hybrid'}),(0,t.jsxs)(a.A,{className:"mt-3",children:["EN 16931 requires either a payment due date (BT-9) or payment terms (BT-20) whenever the invoice total is positive, so the example sets both. Omit them both and the conversion is rejected (BR-CO-25). WooCommerce has a real ",(0,t.jsx)("code",{children:"order.number"}),", so no fallback to the order id is needed."]})]}),b=()=>(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(u,{icon:d.vqh,children:[(0,t.jsx)("p",{className:"mb-1",children:"Deliver the hybrid PDF. Two common options:"}),(0,t.jsxs)("p",{className:"mb-1",children:[(0,t.jsx)("strong",{children:"a) Email it to the customer."})," Attach the PDF buffer from Step 4 to your transactional email (SES, Resend, Postmark, \u2026). The single file is both the readable invoice and the compliant e-invoice, so one attachment covers both."]}),(0,t.jsxs)("p",{className:"mb-1",children:[(0,t.jsx)("strong",{children:"b) Attach it to the order as a Note."})," Upload the PDF to your own storage (or the WordPress Media library), then create a private Order Note linking to the archive so the document lives next to the record. With the Consumer Key + Secret from Step 1:"]}),(0,t.jsx)(h.A,{language:"bash",className:"my-3",children:'curl -u "$WC_CONSUMER_KEY:$WC_CONSUMER_SECRET" \\\n -X POST "https://your-shop.example/wp-json/wc/v3/orders/$ORDER_ID/notes" \\\n -H "Content-Type: application/json" \\\n -d \'{"note":"E-invoice: https://.../WC-1042.pdf","customer_note":false}\''})]}),(0,t.jsxs)(a.A,{className:"mt-3",children:["Drop the ",(0,t.jsx)("code",{children:"X-Sandbox: true"})," header when you go live: sandbox conversions are watermarked and don't count against quota, which is perfect for building this flow but not for real invoices."]})]}),f=()=>(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("p",{children:["Back-fill a compliant PDF/A-3 archive for orders you've already completed (useful for the EU's multi-year invoice-retention requirements), writing each archive straight to your own cloud bucket.",(0,t.jsx)("br",{}),(0,t.jsx)("code",{children:"List completed orders \u2192 PolyDoc API (+ cloudStorage) \u2192 PDF/A-3b in your bucket"})]}),(0,t.jsx)(l.A,{className:"mt-3",children:(0,t.jsxs)("span",{children:["Same mapping as Use Case 1, run in a loop. The only addition is"," ",(0,t.jsx)("code",{children:"cloudStorage.presignedUrl"}),", which tells PolyDoc to upload the result directly to your storage instead of returning the bytes, so large back-fills never flow back through your code."]})})]}),w=()=>(0,t.jsxs)(u,{icon:d.vkd,children:[(0,t.jsx)("p",{className:"mb-1",children:"Decide the window to archive (e.g. the last fiscal year) and list the completed orders. Use the WooCommerce REST API with HTTP Basic Auth (your Consumer Key + Consumer Secret from Step 1):"}),(0,t.jsx)(h.A,{language:"bash",className:"my-3",children:'curl -u "$WC_CONSUMER_KEY:$WC_CONSUMER_SECRET" -G \\\n "https://your-shop.example/wp-json/wc/v3/orders" \\\n -d "status=completed" \\\n -d "after=2025-01-01T00:00:00" \\\n -d "per_page=100" \\\n -d "page=1"'}),(0,t.jsxs)("p",{className:"mt-2",children:["Page through with the ",(0,t.jsx)("code",{children:"page"})," param (and the"," ",(0,t.jsx)("code",{children:"X-WP-TotalPages"})," response header) until you've collected every order in range. The response already includes inline"," ",(0,t.jsx)("code",{children:"line_items[]"}),", so there is no second call per order."]})]}),_=()=>(0,t.jsxs)(u,{icon:d.oI1,children:[(0,t.jsxs)("p",{className:"mb-1",children:["For each order, map it exactly as in Use Case 1, then POST to PolyDoc with a ",(0,t.jsx)("code",{children:"cloudStorage.presignedUrl"}),": a pre-signed PUT URL you generate for your S3 / R2 / GCS / Azure bucket. PolyDoc renders, builds the hybrid, and PUTs it there; the response is just a status:"]}),(0,t.jsx)(h.A,{language:"javascript",className:"my-3",children:'for (const order of completedOrders) {\n const body = mapToPolydoc(order); // Step 4 mapping\n body.cloudStorage = {\n presignedUrl: await presignPut(`invoices/${order.number}.pdf`),\n };\n\n await fetch("https://api.polydoc.tech/pdf/convert", {\n method: "POST",\n headers: {\n Authorization: "Bearer YOUR_API_KEY",\n "Content-Type": "application/json",\n },\n body: JSON.stringify(body),\n });\n}'}),(0,t.jsxs)("p",{className:"mt-2",children:["See the"," ",(0,t.jsx)("a",{href:"/guides/cloud-storage/",children:"cloud-storage guides"})," ","for generating presigned URLs per provider."]})]}),P=()=>(0,t.jsxs)(u,{icon:d.JGI,children:[(0,t.jsx)("p",{className:"mb-1",children:"Keep a small index so you can find any archived invoice later. Map the order number to its archive location and retention deadline:"}),(0,t.jsx)("div",{className:"overflow-x-auto my-3",children:(0,t.jsxs)("table",{className:"table w-full [&_td]:text-sm [&_th]:text-sm",children:[(0,t.jsx)("thead",{children:(0,t.jsxs)("tr",{children:[(0,t.jsx)("th",{children:"order_number"}),(0,t.jsx)("th",{children:"archive_url"}),(0,t.jsx)("th",{children:"issued"}),(0,t.jsx)("th",{children:"retain_until"})]})}),(0,t.jsxs)("tbody",{children:[(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:"WC-1042"}),(0,t.jsx)("td",{children:"s3://invoices/WC-1042.pdf"}),(0,t.jsx)("td",{children:"2026-02-15"}),(0,t.jsx)("td",{children:"2036
1-02-15"})]}),(0,t.jsxs)("tr",{children:[(0,t.jsx)("td",{children:"WC-1043"}),(0,t.jsx)("td",{children:"s3://invoices/WC-1043.pdf"}),(0,t.jsx)("td",{children:"2026-02-18"}),(0,t.jsx)("td",{children:"2036-02-18"})]})]})]})}),(0,t.jsx)(a.A,{className:"mt-3",children:"Each archived file is a verified PDF/A-3b hybrid, valid for long-term archival and machine-readable by any system that consumes Factur-X / ZUGFeRD."})]}),C={sidebar_position:26,toc_min_heading_level:2,toc_max_heading_level:3,description:"Generate EU-compliant e-invoices from WooCommerce orders by mapping the Order updated webhook payload to the PolyDoc API and receiving a Factur-X / ZUGFeRD PDF/A-3 hybrid.",title:"WooCommerce e-invoicing: Factur-X and ZUGFeRD",sidebar_label:"WooCommerce"},A="WooCommerce",S={},D=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Use Case 1: Compliant E-Invoice on a Completed Order",id:"use-case-1-compliant-e-invoice-on-a-completed-order",level:2},{value:"Register a webhook for Order updated",id:"register-a-webhook-for-order-updated",level:3},{value:"Verify the webhook HMAC",id:"verify-the-webhook-hmac",level:3},{value:"Read the order from the payload",id:"read-the-order-from-the-payload",level:3},{value:"Map the order and call PolyDoc",id:"map-the-order-and-call-polydoc",level:3},{value:"Deliver the hybrid PDF",id:"deliver-the-hybrid-pdf",level:3},{value:"Use Case 2: Bulk Historical PDF/A-3 Archive",id:"use-case-2-bulk-historical-pdfa-3-archive",level:2},{value:"List the orders to archive",id:"list-the-orders-to-archive",level:3},{value:"Generate and store each archive",id:"generate-and-store-each-archive",level:3},{value:"Index the archive",id:"index-the-archive",level:3},{value:"Next steps",id:"next-steps",level:2}];function N(e){const r={code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",strong:"strong",ul:"ul",...(0,s.R)(),...e.components},{ExternalLink:n}=r;return n||function(e,r){throw new Error("Expected "+(r?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("ExternalLink",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(r.header,{children:(0,t.jsx)(r.h1,{id:"woocommerce",children:"WooCommerce"})}),"\n",(0,t.jsx)(r.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,t.jsxs)(r.ul,{children:["\n",(0,t.jsxs)(r.li,{children:["A PolyDoc account and API key. ",(0,t.jsx)(n,{href:"https://dashboard.polydoc.tech/register?utm_source=docs&utm_medium=integration_guide&utm_campaign=guides&utm_content=woocommerce",children:"Sign up for free"}),", no credit card required; the free plan includes 150 PDF conversions per month."]}),"\n",(0,t.jsxs)(r.li,{children:["A PolyDoc invoice template. Note its short ID for the ",(0,t.jsx)(r.code,{children:"source"})," field (e.g. ",(0,t.jsx)(r.code,{children:"[template:jlE-whg]"}),")."]}),"\n",(0,t.jsxs)(r.li,{children:["A WordPress site with WooCommerce installed and REST API access (Settings, Advanced, REST API). ",(0,t.jsx)(n,{href:"https://woocommerce.com/",children:"Get WooCommerce"}),", it is free."]}),"\n"]}),"\n",(0,t.jsx)(r.h2,{id:"use-case-1-compliant-e-invoice-on-a-completed-order",children:"Use Case 1: Compliant E-Invoice on a Completed Order"}),"\n",(0,t.jsx)(p,{}),"\n",(0,t.jsx)(r.h3,{id:"register-a-webhook-for-order-updated",children:"Register a webhook for Order updated"}),"\n",(0,t.jsx)(j,{}),"\n",(0,t.jsx)(r.h3,{id:"verify-the-webhook-hmac",children:"Verify the webhook HMAC"}),"\n",(0,t.jsx)(g,{}),"\n",(0,t.jsx)(r.h3,{id:"read-the-order-from-the-payload",children:"Read the order from the payload"}),"\n",(0,t.jsx)(y,{}),"\n",(0,t.jsx)(r.h3,{id:"map-the-order-and-call-polydoc",children:"Map the order and call PolyDoc"}),"\n",(0,t.jsx)(v,{}),"\n",(0,t.jsx)(r.h3,{id:"deliver-the-hybrid-pdf",children:"Deliver the hybrid PDF"}),"\n",(0,t.jsx)(b,{}),"\n",(0,t.jsx)(r.h2,{id:"use-case-2-bulk-historical-pdfa-3-archive",children:"Use Case 2: Bulk Historical PDF/A-3 Archive"}),"\n",(0,t.jsx)(f,{}),"\n",(0,t.jsx)(r.h3,{id:"list-the-orders-to-archive",children:"List the orders to archive"}),"\n",(0,t.jsx)(w,{}),"\n",(0,t.jsx)(r.h3,{id:"generate-and-store-each-archive",children:"Generate and store each archive"}),"\n",(0,t.jsx)(_,{}),"\n",(0,t.jsx)(r.h3,{id:"index-the-archive",children:"Index the archive"}),"\n",(0,t.jsx)(P,{}),"\n",(0,t.jsx)(r.h2,{id:"next-steps",children:"Next steps"}),"\n",(0,t.jsxs)(r.ul,{children:["\n",(0,t.jsxs)(r.li,{children:[(0,t.jsx)(r.strong,{children:"New to PolyDoc?"})," ",(0,t.jsx)(n,{href:"https://dashboard.polydoc.tech/register?utm_source=docs&utm_medium=integration_guide&utm_campaign=guides&utm_content=woocommerce",children:"Create a free account"})," - 150 conversions per month, no credit card."]}),"\n",(0,t.jsxs)(r.li,{children:[(0,t.jsx)(r.strong,{children:"EU e-invoicing:"})," see the ",(0,t.jsx)(n,{href:"https://polydoc.tech/e-invoice/",children:"e-invoicing overview"})," for how PolyDoc generates Factur-X and ZUGFeRD hybrid PDFs. It produces the compliant document; pair it with your own Peppol transmission and archiving."]}),"\n",(0,t.jsxs)(r.li,{children:[(0,t.jsx)(r.strong,{children:"Compare options:"})," see how PolyDoc ",(0,t.jsx)(n,{href:"https://polydoc.tech/alternatives/",children:"compares to other PDF and e-invoice APIs"}),"."]}),"\n"]})]})}function k(e={}){const{wrapper:r}={...(0,s.R)(),...e.components};return r?(0,t.jsx)(r,{...e,children:(0,t.jsx)(N,{...e})}):N(e)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.