APIDeveloperE-CommerceSustainability

Hydrogen Headless: Trees and Donations Guide

GoodAPI Team ·

Hydrogen is Shopify’s own opinionated stack for custom storefronts, and it does not behave like an Online Store theme. It runs on React Router, talks to Shopify through the Storefront API and Customer Account API clients, ships a Cart API for cart state, and deploys to Oxygen. That architecture is exactly why Shopify Hydrogen tree planting looks different from the one-click install most merchants describe, and why headless Shopify donations cannot live in a Liquid section that no longer exists. There is no theme block to drop a widget into. What you get instead is a server route you own, which is a better home for impact logic anyway.

Why Hydrogen Storefronts Need an API, Not a Widget

On the Online Store, a sustainability app earns its keep by rendering into places you do not control: a cart drawer section, a product page app block, a checkout extension. That delivery mechanism assumes Liquid and the theme editor.

Start a Hydrogen project with npm create @shopify/hydrogen@latest and none of that exists. Your cart drawer is a React component you wrote, fed by Hydrogen’s Cart API. Your product page is a route with a Storefront API query in its loader. The Hydrogen documentation describes a framework, not a theme, and a framework has no slot for someone else’s markup. If you want a tree planted per order, you write the call yourself, and you decide the trigger, volume, attribution, and failure behavior.

Where Impact Belongs in a Hydrogen App

The most common mistake in a headless build is putting the impact call too early and too far forward.

Not the browser, and not the cart

A cart mutation is not a purchase. Hydrogen’s Cart API makes it easy to hang logic off a line item add or a cart attribute update, and both fire constantly during a shopping session. Plant on those and you fund trees for abandoned carts. Anything that runs in the browser also needs a key in the browser, and a planting key in a client bundle is a public key anyone can spend against.

Yes to a server route on Oxygen

Oxygen hosts Hydrogen and deploys your route files as serverless endpoints alongside the storefront. A Hydrogen route file that exports an action and no default component is a resource route: no UI, just a server endpoint on every deploy. Point the Shopify orders/paid webhook at it and you have a production shaped pipeline. The same handler also runs unchanged on a Cloudflare Worker or any service that already processes your order events.

Shopify Hydrogen Tree Planting: Two Install Paths

Merchants on Hydrogen usually have one of two setups, and they lead to different answers.

What you get Shopify App Store install Hydrogen route + REST key
Best for Orders created in Shopify Admin Orders created by your Hydrogen storefront
Code required None One resource route and one fetch call
Where it runs Managed by the app Oxygen, or any server you operate
Trigger control Per order, per product, round-ups Anything your handler can express
Credentials App install scopes live_sk_ and test_sk_ keys
Duplicate protection Handled for you You pass idempotency_key

For the managed path, the GoodAPI app on the Shopify App Store sits at 5.0 stars across 221 reviews and configures from the admin with no code. That path still matters when orders are created in Shopify Admin through draft orders, POS, wholesale, or subscription contracts even while the public storefront is headless.

For the storefront path, prefer standalone REST keys from app.thegoodapi.com (live_sk_ / test_sk_) in Oxygen environment variables, and build against the test key first. Running both paths is fine: the idempotency key keeps them from colliding on the same order.

Step by Step: Shopify Hydrogen Tree Planting on Oxygen

1

Get a test key first

Sign up at app.thegoodapi.com and grab both keys. A test_sk_ key behaves exactly like production but never charges you and never plants a real tree, so build and replay against it until the handler is boring.

2

Add the key to your Oxygen environment

Store GOODAPI_KEY and your Shopify webhook signing secret as Oxygen environment variables so they reach your route through context.env. Do not put either in a .env file you commit, and never expose them to a loader that feeds a client component.

3

Create the resource route

Add a route file such as app/routes/webhooks.orders-paid.tsx that exports only an action. With no default export there is no UI, and Oxygen deploys it as a plain endpoint at that path.

4

Verify the webhook before you spend anything

Check the HMAC over the raw request body before parsing it. An unverified endpoint is a public button that drains your planting budget, and re-serialized JSON will never match the signature.

5

Plant on paid, with an idempotency key

Call POST https://app.thegoodapi.com/plant/trees with the Shopify order ID as the idempotency_key. Shopify retries deliveries for days after a failure, and that one field is what turns every retry into the same single planting.

6

Subscribe the webhook and watch the first live orders

Register orders/paid against your deployed Oxygen URL, switch to the live key, and reconcile the first day of plantings against your order list before you announce anything on the storefront.

The resource route

app/routes/webhooks.orders-paid.tsx
import type {ActionFunctionArgs} from 'react-router';
export async function action({request, context}: ActionFunctionArgs) {
const raw = await request.text();
const signature = request.headers.get('x-shopify-hmac-sha256');
if (!(await isValidShopifyWebhook(raw, signature, context.env.SHOPIFY_WEBHOOK_SECRET))) {
return new Response('Invalid signature', {status: 401});
}
const order = JSON.parse(raw);
if (order.financial_status !== 'paid') {
return new Response('Ignored', {status: 200});
}
const res = await fetch('https://app.thegoodapi.com/plant/trees', {
method: 'POST',
headers: {
Authorization: context.env.GOODAPI_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
count: 1,
attribution: order.email,
idempotency_key: String(order.id),
metadata: {order_id: String(order.id), source: 'hydrogen-oxygen'},
}),
});
if (!res.ok) {
return new Response('Retry me', {status: 500});
}
return new Response('OK', {status: 200});
}

Returning a 500 on a failed GoodAPI call is deliberate. Shopify treats it as a failed delivery and retries, and because the order ID is the idempotency key, a later success is still exactly one tree. Implement isValidShopifyWebhook as an HMAC-SHA256 over the raw body (base64 encoded) with the Web Crypto API Oxygen already provides, and always verify before JSON.parse.

Reading Evidence Back Into Your Storefront

Planting quietly wastes a good story, and Hydrogen makes the display half easy: a loader is already a server function with your key in scope.

GET https://app.thegoodapi.com/evidence returns aggregated verification evidence, and a per-tree evidence endpoint returns the timeline for a single tree: registered, funded, allocated, then physically planted with geotagged proof. GoodAPI’s reforestation partner is Veritree, which is where the GPS verification comes from, so the claim on your impact page has a named third party behind it.

Fetch that in a loader for a route such as app/routes/impact.tsx, cache the response, and render it with normal React. An order confirmation route can do the same scoped to one order.

Adding Headless Shopify Donations

GoodAPI donations reach more than 1.3 million verified 501(c)(3) nonprofits, handle commercial co-venture compliance, and take a 0% platform fee on the donated amount. Donations run on the Give plan at $15 per month.

In Hydrogen the wiring matches the planting flow, and owning the cart is the advantage. Add a round-up or donation line in the Cart API, carry that attribute to the paid webhook, and have your handler send the donation with the same idempotency key it uses for trees. For endpoint detail, see the charity donations API developer guide.

Pricing for Hydrogen Teams

The Grow plan is free to install and includes your first 50 trees and 100 ocean-bound plastic bottles at no charge. After that trees are $0.43 each and bottles are $0.05 each, billed on one end-of-month invoice with no monthly fee for trees and plastic.

Donations run on the Give plan at $15 per month, and checkout round-ups plus API access run on the Lead plan at $49 per month. There is no headless surcharge, because the Shopify app and your Oxygen route call the same API underneath.

Common Pitfalls

Planting from a cart mutation. Gate on the paid or confirmed order event instead.

No idempotency key. Set idempotency_key to the order ID so Shopify retries resolve to one planting.

A key reachable from the client. Keep the key in context.env and only touch it from an action or a loader that does not forward it.

Trusting the request body before verifying it. Parse after the HMAC check against the raw text.

Assuming the storefront sees every order. Draft orders, POS, and subscription renewals never pass through your Hydrogen route. Widen webhook coverage or let the Shopify app cover those.

Best for

Shopify Hydrogen and Oxygen teams that want verified per-order tree planting, plastic removal, or charity donations wired into a server route they control rather than a theme widget they cannot use

Getting Started

Hydrogen tree planting is one resource route, one verified webhook, and one API call.

If your stack looks different, the same pattern is written up elsewhere: the headless Next.js and React guide covers a generic App Router backend, while Webflow and Ecwid cover hosted carts with their own webhooks. For a provider view, see the best tree planting API comparison.

Grab a test key, add the route, and put a verified tree behind your next Hydrogen order.