Skip to main content
By the end of this guide you’ll have a real Express endpoint that verifies AgentaOS’s signature and marks an order paid the moment a checkout completes, safely, even if the same event is delivered more than once.

Before you start

  • Node.js 20+, Express, and @agentaos/pay installed (npm install @agentaos/pay express).
  • A URL AgentaOS can reach over HTTPS. For local development, run a tunnel (ngrok or similar) so http://localhost:3000 gets a public HTTPS address to register.
  • A payment link or checkout to test against, see Accept your first payment if you don’t have one yet.
1

Register a webhook URL and reveal the signing secret

Open app.agentaos.aiDeveloperWebhooks, and enter the HTTPS URL you want events sent to, for example https://myshop.com/webhooks. Click Reveal signing secret, copy the whsec_... value, and add it to your server’s environment as AGENTAOS_WEBHOOK_SECRET. Never commit it, never log it, never send it to the client.
2

Set up the raw-body route

Signature verification signs the raw request body. If a JSON body parser runs first, the reserialized body won’t byte-for-byte match what was signed, and every event will fail verification. Mount express.raw() on the webhook route only, and keep express.json() for everything else.
3

Verify the signature

webhooks.verify() parses the t=...,v1=... header, rejects it if older than 5 minutes, recomputes the HMAC-SHA256 digest in constant time, and returns a typed WebhookEvent, or throws WebhookVerificationError if anything doesn’t check out.
4

React to checkout.session.completed, idempotently

Key your fulfillment logic off event.data.sessionId, and check whether you’ve already processed it before doing anything. Retries mean the same event can arrive more than once, your handler needs to be safe to run twice.
event.data.amount is a string ("49.99"), not a number. Every other amount you pass into a create call is a plain number, webhook payloads are the one place it’s serialized as a string. Parse it before doing arithmetic.
5

Respond 200 quickly

Verify, queue or record the event, then respond. Do slow work, emails, external API calls, outside the request so AgentaOS doesn’t time out waiting for you.

Full example

webhook-server.ts
Not using Node? The signing algorithm is plain HMAC-SHA256 over {timestamp}.{raw_body}, straightforward to reimplement in any language. See manual verification in Python, Go, and PHP.

Test it end to end

1

Start your server

Run the example above, and make sure your tunnel or production URL points at it.
2

Create a checkout with a webhookUrl

Use the link or checkout from Accept your first payment, or create a new one with webhookUrl set to your endpoint.
3

Pay with the test card

4242 4242 4242 4242, any future expiry, any CVC, typed directly into the hosted checkout page.
4

Watch your server log the order as paid

You should see Order <sessionId> paid: 49.99 EUR in your logs within seconds of the payment clearing.

Verify it worked

  • Your endpoint returned 200 for the delivery (check Developer → Webhooks in the dashboard for delivery status).
  • Your logs show exactly one fulfillment for that sessionId, even if AgentaOS retries the delivery.
  • An invalid or missing X-AgentaOS-Signature header gets rejected with 400, not silently processed. Try POSTing a fake payload without a valid signature to confirm webhooks.verify() throws as expected.

Next steps

Event reference

Every event type, its full payload, and a JSON example.

Webhooks (concept)

Manual verification in Python, Go, and PHP, plus retry and delivery details.

Accept your first payment

Create the checkout that triggers this handler.

Payouts

What happens to your balance after the payment lands.