React When a Bid Is Accepted
Use this recipe when sales needs an instant heads-up the moment a customer accepts a bid — into Slack, into a CRM, or to kick off project provisioning. The pattern generalizes to any webhook event.
-
Register the endpoint.
Terminal window curl -s -X POST https://app.buildworkpro.com/api/v1/webhook-endpoints \-H "Authorization: Bearer ${BUILDWORKPRO_API_KEY}" \-H "Content-Type: application/json" \-d '{"url": "https://hooks.example.com/bwp/bid-accepted","eventTypes": ["bid.accepted"]}'Response includes a
signingSecret— store it in your secrets manager. It’s shown only at creation time. -
Stand up a receiver that verifies the signature.
The signature is HMAC-SHA256 over
${timestamp}.${rawBody}, encoded as hex. Useexpress.raw()so you have the bytes before any JSON parser mutates them.import express from 'express';import { createHmac, timingSafeEqual } from 'node:crypto';const app = express();const SECRET = process.env.BWP_BID_ACCEPTED_SECRET;const seen = new Set(); // swap for Redis or a DB in productionapp.post('/bwp/bid-accepted', express.raw({ type: 'application/json' }), (req, res) => {const sig = req.header('BuildWorkPro-Signature') ?? '';const t = Number(/(?:^|,)t=(\d+)/.exec(sig)?.[1]);const v1 = /(?:^|,)v1=([0-9a-f]+)/.exec(sig)?.[1];if (!t || !v1) return res.status(400).end();if (Math.abs(Date.now() / 1000 - t) > 300) return res.status(400).end();const expected = createHmac('sha256', SECRET).update(`${t}.${req.body.toString('utf8')}`).digest('hex');const ok =expected.length === v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));if (!ok) return res.status(400).end();const event = JSON.parse(req.body.toString('utf8'));if (seen.has(event.id)) return res.status(200).end();seen.add(event.id);handleBidAccepted(event).catch(console.error);res.status(200).end();}); -
Forward into Slack.
async function handleBidAccepted(event) {await fetch(process.env.SLACK_WEBHOOK_URL, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({text: `Bid accepted: bid ${event.data.id} is now ${event.data.status}.`,}),});} -
Test with the replay endpoint.
Once you have a real delivery in Settings -> Developer -> Webhooks, click into it and hit Replay to re-fire the same event id. Your dedupe check should make the second delivery a no-op.