Webhooks and Callbacks

Neon regularly updates you about your users as they move through our flow via event-based updates.

These events come in two forms:

  • Webhooks are purely informational. You aren't required to register a URL to listen to these events, and Neon ignores any data in the response body.

    Examples of webhooks include:

    • purchase completion (purchase.completed)
    • refund processed (refund.processed)
  • Callbacks are real-time events that affect the user's flow. You'll register URLs to listen to these as part of your initial setup. Your responses to these events affect the user's experience, so we require a specific response schema from your server. If we don't hear a response within five seconds, we retry the request.

    Examples of callbacks include:

    • get account inventory (account.getInventory)
    • account validation (account.validate)

Webhooks and callbacks are key to understanding your users' journeys through the Neon flow. In order to get these updates, you'll need to create an endpoint that can respond to our requests, and then register that endpoint with our servers via the API or the dashboard. This page will guide you through the process of setting up a server to listen for updates, and registering that server to receive updates.

Prerequisites

Before you begin, make sure you have the following prerequisites in place:

  • A server or hosting environment where you can run your webhook listener.
  • A shared secret, provided by Neon when you register your listener. You can either use a Neon-supplied secret, or provide one of your own.

In addition, you'll need to be familiar with the request and response schemas for each webhook and callback your listener will be subscribed to. Each event's schema is documented in the API docs under a specific callback or webhook registration URL. For example, schemas for callbacks sent during the checkout flow are available here.

Listening for events

Set up a server

First, you'll need to set up your server to listen for incoming webhook data. Here's a simple example using Node.js and Express:

import express from "express";

const app = express();
const port = 4000; // Set your desired port

// Configure body parser to handle JSON data
app.use(express.json());

app.post("/webhook", (req, res) => {
  // Process data here
});

// Start the server
app.listen(port, () => {
  console.log(`Webhook listener server is running on port ${port}`);
});

Validate request signature

Before parsing any data, you should first validate that the event actually originated from Neon. You can do this by validating the request signature with the shared secret you configure when registering the event listener. This is an optional, but highly recommended, step, as it helps keep your servers safe from malicious agents.

We generate these signatures by generating a cryptographic HMAC digest of the event data, using the shared secret you configure on listener registration as the HMAC secret. This digest is passed to you via the x-neon-digest request header on every request. You can validate that the request is genuine by recreating the HMAC digest of the JSONified event data using the shared secret.

Here's an example implementation using Node.js and Express:

import crypto from "crypto";

// Your shared secret
const sharedSecret = "a_reasonably_secure_shared_secret!";

const verifySignature = (eventData: string, signature: string) => {
  const hash = crypto.createHmac("sha256", sharedSecret).update(eventData).digest("hex");
  return signature === hash;
};

// In your webhook endpoint route
app.post("/webhook", (req, res) => {
  const eventData = JSON.stringify(req.body);
  const receivedSignature = req.headers["x-neon-digest"];

  if (typeof receivedSignature === "string" && verifySignature(eventData, receivedSignature)) {
    // process data here
    res.status(200).send("Valid event signature");
  } else {
    // Invalid signature, reject the data
    res.status(403).send("Invalid event signature");
  }
});

Parse event data and respond

Once you've validated the event digest, you can safely parse the event data and respond to Neon's server. Each event has a separate request and response body, but in general, the schema will have the following structure:

{
  "id": "756e925f-edb6-4cdd-ad26-a39aecdba0aa", // unique ID for this request
  "type": "purchase.completed", // this event's name
  "version": 2, // this event's version
  "isSandbox": true, // whether this event originated from your sandbox environment
  data: {} // data specific to this event type
}

Once you've parsed the data, you'll need to respond to our server. Webhooks don't expect any response data, and any data that's submitted will be ignored. Callbacks expect a response matching a specific schema, as specified in each event's API docs.

Retries and errors

In general, we retry failed webhook requests for up to 36 hours, and callback requests up to 3 times. Any non-2XX response status is considered to be an error and is retried. The data sent between each retry is identical. Retries are performed with exponential backoff between attempts, with each attempt taking place 2^(attempt number) - 1 seconds after the previous attempt.

Note that 404s in response to the account.getInventory callback are treated as meaning that the account doesn't exist. This is the only case where we don't retry a non-200 status.

We ignore response data sent to webhook calls. Callbacks, on the other hand, require a specific schema. Response schema violations usually result in an error to the user, so it's important to test your callback responses thoroughly.

Testing your webhooks

You can test your webhooks and callbacks by registering them to your sandbox environment. You can do so in the dashboard by toggling over to your sandbox environment, or via the API by passing in your sandbox API key.

Registering an event listener

You can register an event listener in the dashboard, or via the API. You'll need the URL for the listener you just configured, as well as a shared secret.

You'll also need to decide which events you want your URL to listen to. By default, the API configures listeners to receive all events for that particular phase of the flow. You can also pass in a list of events as an override.

Finally, you'll need to decide which event version you'll want to listen to. Any backwards-incompatible changes will be made under a new event version. By default, the API configures listeners to receive events in the most recent format at the time of registration. If a newer event version is released, the format of the data your listener will send and receive will remain unchanged.

Retrieving historical data

Neon stores request data for events transmitted in the past. If your webhook listener experiences any downtime, you can use the webhook events API to query events in the past. We expect to store event data for up to 30 days.