Skip to main content

Overview

This recipe shows you how to set up a webhook endpoint that receives card events from Aptly, verifies the request is genuine, and processes the payload.

What you need

  • A publicly accessible URL for your server (use a tool like ngrok for local development)
  • Your webhook signing key from Card Sources > API
  • Node.js with a web framework such as Express

Step 1: Configure the webhook in Aptly

  1. Open the board in Aptly
  2. Click Card Sources > API
  3. Paste your server URL into the Webhook URL field
  4. Copy the Webhook Signing Key

Step 2: Create your endpoint

Set up a POST endpoint to receive incoming webhook requests from Aptly.
import express from "express";

const app = express();
app.use(express.json());

const SIGNING_KEY = process.env.APTLY_SIGNING_KEY;

app.post("/webhooks/aptly", (req, res) => {
  // Step 3: Verify the signing key
  const receivedKey = req.headers["x-signingkey"];

  if (receivedKey !== SIGNING_KEY) {
    console.warn("Webhook rejected: invalid signing key");
    return res.status(403).json({ error: "Forbidden" });
  }

  // Step 4: Process the payload
  const card = req.body;
  console.log("Received card update:", card._id);

  // Return 200 immediately before doing any slow work
  res.status(200).send("OK");

  // Do your processing asynchronously after responding
  processCardUpdate(card);
});

app.listen(3000, () => console.log("Webhook server running on port 3000"));

Step 3: Handle the payload

The webhook payload contains the full card data as key/value pairs matching your board’s field structure.
{
  "_id": "JShpBxEFgRmZnivTf",
  "Name": "Jane Smith",
  "Stage": "In Progress",
  "Email": "jane@example.com",
  "Unit": "4B"
}
Process it based on whatever your integration needs to do.
async function processCardUpdate(card) {
  if (card.Stage === "Completed") {
    await markWorkOrderComplete(card._id);
  } else if (card.Stage === "In Progress") {
    await notifyAssignedTechnician(card);
  }
}

Step 4: Test your endpoint locally

Use ngrok to expose your local server to the internet during development.
ngrok http 3000
Copy the forwarding URL (e.g. https://abc123.ngrok.io) and paste it as your Webhook URL in Aptly. Then trigger an update on a card and watch your server logs.

Things to watch out for

  • Always return 200 as quickly as possible. Aptly waits up to 60 seconds for a response before considering the delivery failed.
  • If your endpoint does not return 200, Aptly will retry the request indefinitely. If you need to take your server offline, update the Webhook URL in Card Sources to a temporary endpoint that returns 200.
  • The signing key is a plain string match, not an HMAC signature. Keep it in an environment variable and never commit it to source control.

Next steps