Webhooks
Send new requests, comments and messages to Make, n8n, Zapier or your own server
A webhook sends what happens on your board to another tool the moment it happens. Use it to add every new request as a row in a spreadsheet, post it to a channel, or create a card in the tool you plan your work in, without writing code.
It works with any tool that can receive a webhook, such as Make, n8n, Zapier (on its paid plans) or your own server.
Set Up
- In your other tool, create a webhook trigger and copy the link it gives you. It starts with
https://. - In FeatureVote, go to Settings → Integrations and find Webhooks.
- Paste the link, tick the events you want and click Save webhook.
- Pick an event next to Send test and click it. Your tool receives an example message, which it uses to learn the fields. Send one test for each event you use.
The card then shows the last delivery, for example Last sent Oct 10, 2026, 10:40 AM UTC · 200.
Recipes
Make: add each new request to Google Sheets
- Create a scenario that starts with Webhooks → Custom webhook, and copy its address.
- Save it in FeatureVote with New request ticked, then click Send test.
- In Make, click Redetermine data structure if it asks, and add Google Sheets → Add a row.
- Map columns to
data.request.title,data.request.url,data.request.author.emailand anything else you need.
n8n
Start a workflow with the Webhook node, set its method to POST, and use its production URL. Send a test from FeatureVote while the node is listening to see the fields.
Zapier
Use Webhooks by Zapier → Catch Hook as the trigger and paste its link. Webhooks by Zapier is part of Zapier's paid plans.
Your own server
Accept a POST with a JSON body, answer with any 2xx status within 10 seconds, and verify the signature (see below).
Changes made through an AI client (MCP) are not sent as webhook events yet.
Events
| Event | Sent when |
|---|---|
request.created | Someone posts a request on the board or in Discord, you add one in FeatureVote, or you turn a message into one. Requests waiting for your approval are sent too, with status pending_approval. |
comment.created | A visitor or your team comments on a request in FeatureVote. |
request.vote_milestone | A request reaches 5, 10, 25, 50 or 100 votes. |
request.shipped | You mark a request done and choose to tell its voters. |
message.received | Someone sends your team a private message from the board. |
The Message
Every message has the same envelope. Request events carry the same request object, so fields you map once work for the others.
{
"id": "msg_2f1c3a3e-5d7b-4f0e-9a43-6c1d1c0b9e5a",
"type": "request.created",
"timestamp": "2026-10-10T10:40:00.000Z",
"data": {
"board": {
"name": "Acme",
"url": "https://featurevote.app/boards/acme/board/main"
},
"request": {
"id": "6f0e1d2c-3b4a-4c5d-8e9f-0a1b2c3d4e5f",
"title": "Export to CSV",
"description": "A button that downloads every request as a spreadsheet.",
"status": "requested",
"votes": 1,
"private": false,
"url": "https://featurevote.app/boards/acme/board/main?feature=6f0e1d2c-3b4a-4c5d-8e9f-0a1b2c3d4e5f",
"author": { "name": "Ada Lovelace", "email": "ada@example.com" }
}
}
}
The other events add to data:
comment.createdaddscomment, withid,text,internalandauthor.request.vote_milestoneaddsmilestone, the vote count reached.message.receivedhasmessage(withid,textandauthor) in place ofrequest.
votes counts people, not weighted votes. author.name and author.email are null when the person did not give them. Text fields are cut at 5,000 characters.
Messages include the names and email addresses people gave you, so only send them to a tool you trust with that.
Verifying the Signature
Messages are signed the Standard Webhooks way, so you can verify them with one of its libraries. Each request carries three headers:
webhook-id, the same as the messageid. Use it to skip a message you have already handled.webhook-timestamp, in seconds.webhook-signature, in the formv1,<signature>.
Copy your signing secret with Copy signing secret on the card. To check a message by hand in Node:
import crypto from 'node:crypto';
function isFromFeatureVote(secret, headers, rawBody) {
const key = Buffer.from(secret.replace('whsec_', ''), 'base64');
const signed = `${headers['webhook-id']}.${headers['webhook-timestamp']}.${rawBody}`;
const expected = crypto.createHmac('sha256', key).update(signed).digest('base64');
return headers['webhook-signature'] === `v1,${expected}`;
}
Removing the webhook and adding it again gives it a new secret.
Delivery
- Each event is sent once. A message that fails is not sent again, and the card shows why it failed.
- Your tool has 10 seconds to answer. Redirects are not followed.
- The link must be a public
https://address.
Have a tool you'd like us to connect to directly? Request it on our feature board.