Webhooks
Receive real-time HTTP notifications when events occur in your account
Overview
GrowQR Webhooks deliver real-time HTTP POST notifications to your server whenever key events happen in your workspace — a link is clicked, a new URL is created, a campaign is updated, a conversion is recorded, or a landing page is published. Instead of polling the API to check for changes, your systems are notified the instant something happens.
Webhooks are the backbone of event-driven integrations. Use them to sync data with your CRM, trigger Slack notifications, update analytics dashboards, kick off automation workflows, or feed events into a data warehouse — all in real time.
What Problem It Solves
API polling is wasteful and slow. If you check the API every 60 seconds for new clicks, you're making 1,440 requests per day — most returning no new data — and you still have up to 60 seconds of latency. At scale, polling burns API quota, increases server costs, and delivers a poor real-time experience.
Webhooks flip the model: GrowQR pushes events to you. You receive data within seconds of the event, with zero wasted requests. Your integration code becomes simpler (just an HTTP endpoint that accepts POST requests) and your data stays fresh.
How It Works
When an event occurs in your workspace, GrowQR's event system:
- Serializes the event into a JSON payload with a consistent schema.
- Signs the payload using HMAC-SHA256 with your webhook's secret key, placing the signature in the
X-GrowQR-Signatureheader. - Delivers the payload via HTTP POST to your configured endpoint URL.
- Handles failures with automatic retries using exponential backoff (3 attempts over 1 hour).
Available Events
| Event | Trigger |
|---|---|
url.created | A new short link is created |
url.clicked | A short link receives a click |
url.updated | A short link's properties are modified |
url.deleted | A short link is deleted |
campaign.created | A new campaign is created |
campaign.updated | A campaign's settings are changed |
campaign.deleted | A campaign is deleted |
conversion.created | A new conversion event is tracked |
landing_page.published | A landing page is published or updated |
You can subscribe to individual events or select All Events to receive everything.
Step-by-Step Usage
Creating a Webhook
- Navigate to Dashboard → Settings → Webhooks.
- Click Create Webhook.
- Enter the Endpoint URL — this must be a publicly accessible HTTPS URL that accepts POST requests. For example:
https://api.yourcompany.com/webhooks/growqr. - Select the Events you want to receive (or choose All Events).
- Click Create. GrowQR generates a signing secret displayed once — copy and store it securely.
Webhook Created
──────────────────────────────────────────
Endpoint: https://api.yourcompany.com/webhooks/growqr
Events: url.created, url.clicked, conversion.created
Secret: whsec_a1b2c3d4e5f6... (shown once)
Status: Active
Payload Format
Every webhook delivery sends a JSON payload with the following structure:
{
"id": "evt_7f3a9b2c4d5e6f",
"event": "url.clicked",
"created_at": "2026-03-05T14:23:01Z",
"data": {
"link_id": "link_abc123",
"short_url": "https://go.yourcompany.com/demo",
"destination_url": "https://yourcompany.com/product/demo",
"click": {
"timestamp": "2026-03-05T14:23:01Z",
"ip_country": "US",
"ip_city": "San Francisco",
"device": "mobile",
"browser": "Chrome",
"os": "iOS",
"referrer": "https://twitter.com"
}
}
}The data object varies by event type. URL events include link details, campaign events include campaign metadata, and conversion events include conversion value and attribution data.
HMAC Signature Verification
Every webhook request includes an X-GrowQR-Signature header containing an HMAC-SHA256 signature of the raw request body. Always verify this signature to confirm the request genuinely came from GrowQR and wasn't tampered with.
Node.js verification example:
const crypto = require('crypto');
function verifyWebhookSignature(req, secret) {
const signature = req.headers['x-growqr-signature'];
if (!signature) return false;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Express.js middleware
app.post('/webhooks/growqr', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = verifyWebhookSignature(
{ headers: req.headers, rawBody: req.body },
process.env.GROWQR_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
switch (event.event) {
case 'url.clicked':
handleClick(event.data);
break;
case 'url.created':
handleNewLink(event.data);
break;
case 'conversion.created':
handleConversion(event.data);
break;
default:
console.log(`Unhandled event: ${event.event}`);
}
res.status(200).json({ received: true });
});Key points:
- Use
crypto.timingSafeEqualto prevent timing attacks. - Parse the raw body for signature computation, not a parsed JSON object.
- Return
200promptly. Process events asynchronously if your handler is slow.
Retry Mechanism
If your endpoint returns a non-2xx status code or fails to respond within 30 seconds, GrowQR retries the delivery automatically with exponential backoff:
| Attempt | Delay After Failure |
|---|---|
| 1st retry | ~30 seconds |
| 2nd retry | ~2 minutes |
| 3rd retry | ~8 minutes |
| 4th retry | ~32 minutes |
| 5th retry | ~2 hours |
Retries use an exponential backoff formula (base delay × 2^retryCount) with the system checking for pending retries every 30 seconds. After five failed attempts (configurable per webhook), the delivery is marked as Failed in the delivery log.
The delivery log shows retry status for each event, including the current retry count and the time of the next scheduled retry. You can also manually redeliver any failed event from the dashboard.
If a webhook endpoint fails consistently (10 consecutive failures), GrowQR automatically disables the webhook and sends an email notification to workspace Admins and the Owner.
Delivery History
- Navigate to Dashboard → Settings → Webhooks.
- Click on a webhook to open its detail view.
- The Deliveries tab shows a log of every delivery attempt with:
- Timestamp
- Event type
- HTTP status code returned by your endpoint
- Response time
- Payload (expandable)
Use the delivery log to debug integration issues, verify payloads, and manually redeliver failed events.
Testing Webhooks
Before wiring up a production endpoint, test your webhook configuration:
- Use a service like webhook.site or RequestBin to get a temporary endpoint URL.
- Create a webhook in GrowQR pointed at that temporary URL.
- Trigger an event (e.g., create a new short link).
- Check the testing service to see the delivered payload and headers.
- Verify the
X-GrowQR-Signatureheader against your signing secret.
Alternatively, use the Send Test Event button in the webhook detail view. GrowQR sends a sample payload for each subscribed event so you can verify your endpoint handles them correctly.
Best Practices
- Always verify signatures. Never trust a webhook payload without validating the HMAC signature. An unverified endpoint is vulnerable to spoofed events.
- Return 200 quickly and process asynchronously. If your event handler involves database writes, API calls, or heavy computation, enqueue the work and return
200immediately. A slow response triggers unnecessary retries. - Implement idempotency. Webhook deliveries may be duplicated due to retries or network issues. Use the
idfield in the payload to deduplicate events. Store processed event IDs and skip any you've already handled. - Subscribe only to the events you need. Receiving
url.clickedfor a high-traffic link can generate thousands of deliveries per hour. If you only needurl.created, don't subscribe to click events. - Use HTTPS endpoints exclusively. GrowQR only delivers webhooks to HTTPS URLs. Ensure your SSL certificate is valid and not self-signed.
- Monitor delivery success rates in the Webhooks dashboard. A sudden drop in success rate indicates an endpoint issue. Set up alerting on your endpoint's error rate as a backup.
- Rotate your webhook secret periodically. Navigate to the webhook settings and click Regenerate Secret. Update your verification code with the new secret. The old secret remains valid for 24 hours to avoid dropped deliveries during rotation.
Example Workflows
Real-Time Slack Notifications
- Create a webhook subscribing to
url.createdandcampaign.created. - Point the endpoint to a small serverless function (AWS Lambda, Vercel, or Cloudflare Workers).
- The function parses the event, formats a message ("New link created: go.co.com/launch by jane@co.com"), and posts it to a Slack channel via the Slack Incoming Webhooks API.
- Your team sees every new link and campaign the moment it's created.
CRM Sync on Conversion
- Subscribe to
conversion.createdevents. - When a conversion fires, your endpoint looks up the visitor's email (passed via UTM or landing page form) in your CRM.
- The CRM contact is updated with a "Converted via GrowQR" tag and the conversion value.
- Sales reps see real-time conversion data without leaving the CRM.
Data Warehouse Ingestion
- Subscribe to All Events.
- Point the webhook to a lightweight ingestion endpoint that writes raw payloads to a message queue (Kafka, SQS, Pub/Sub).
- A downstream consumer reads from the queue, transforms the data, and loads it into your data warehouse (BigQuery, Snowflake, Redshift).
- Analytics teams query GrowQR events alongside other marketing data for unified reporting.