Developers
Webhooks and Zapier
Webhooks send an HTTP request to your server the moment something happens in your workspace, such as an app being paused or a smart link being clicked. Use them to update your own systems, post to Slack, or connect mapps.one to Zapier, Make or n8n without writing a server. Webhooks are available on the Studio plan, with up to 10 endpoints per workspace.
Add an endpoint
- In the dashboard, open Developers → Webhooks and choose Add endpoint. Workspace owners and admins can do this.
- Enter a public
httpsURL, choose the events to send (or all events) and add a description. - Copy the signing secret and store it with your server's other secrets.
- Choose Send test event to check that your endpoint responds.
The endpoint page shows recent deliveries with the response status, the first 2,000 characters of the response body and how long each attempt took. You can also create and delete endpoints with the API.
Events
| Type | Sent when |
|---|---|
app.created | An app was added to the workspace. |
app.updated | App details, links or page settings changed. |
app.deleted | An app was deleted. |
app.status_changed | An app went live, was paused or was suspended. |
app.metadata_fetched | Store metadata was imported for an app. |
legal.published | A privacy policy or terms document was published. |
domain.verified | A custom domain was verified. |
link.clicked | Every smart link click. This can be a lot of requests for a popular app. |
analytics.daily_summary | The previous day's click totals for each app, sent once a day. |
Request format
Every event is sent as a POST request with a JSON body.
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | mapps.one-Webhooks/1.0 |
X-Mapps-Event | The event type, such as link.clicked |
X-Mapps-Delivery | The event ID. It stays the same on every retry. |
X-Mapps-Timestamp | Unix time of this delivery attempt |
X-Mapps-Signature | t={timestamp},v1={signature} |
The body is an envelope with the same shape for every event:
{
"id": "evt_01j8c3m9t5v2x7z4b6d8f0h2k4",
"type": "app.status_changed",
"created_at": "2026-09-14T09:41:07+00:00",
"workspace": {
"id": "01j8a0f4c7d2e9h6k3m5n8q1r4",
"name": "Habitly Labs"
},
"data": {
"app": {
"id": "01j8b2k6r3xq9v7m4n5p0s1t2w",
"slug": "habitly",
"name": "Habitly"
},
"status": "paused"
}
}
The contents of data depend on the event. Events about an app include an app object with its id, slug and name.
link.clicked
"data": {
"app": { "id": "01j8b2k6r3xq9v7m4n5p0s1t2w", "slug": "habitly", "name": "Habitly" },
"click": {
"occurred_at": "2026-09-14 09:41:07",
"os": "iOS",
"device_type": "mobile",
"country": "TR",
"source": "instagram",
"destination": "app_store",
"utm_source": "instagram",
"utm_medium": "social",
"utm_campaign": "launch"
}
}
occurred_at is in UTC. device_type is mobile, tablet or desktop. destination is a store key, web or landing. Fields that are unknown are null. Bot visits never trigger this event.
app.metadata_fetched
"data": {
"app": { "id": "01j8b2k6r3xq9v7m4n5p0s1t2w", "slug": "habitly", "name": "Habitly" },
"source": "app_store"
}
Verify signatures
Check the signature on every request, so you know it came from mapps.one and was not changed on the way:
- Read
tandv1from theX-Mapps-Signatureheader. - Compute an HMAC-SHA256 of
{t}.{raw request body}using your endpoint's signing secret, as lowercase hex. - Compare it with
v1using a constant-time comparison. - Reject the request if
tis more than five minutes away from your server's clock, to block replayed requests.
PHP
<?php
$secret = getenv('MAPPS_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_MAPPS_SIGNATURE'] ?? ''), $parts);
$timestamp = (int) ($parts['t'] ?? 0);
$expected = hash_hmac('sha256', $timestamp.'.'.$body, $secret);
if (! hash_equals($expected, (string) ($parts['v1'] ?? '')) || abs(time() - $timestamp) > 300) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
if ($event['type'] === 'link.clicked') {
// Store $event['data']['click'] ...
}
http_response_code(200);
In Laravel, read the body with $request->getContent() and the header with $request->header('X-Mapps-Signature'), and exclude the route from CSRF protection.
Node.js (Express)
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const secret = process.env.MAPPS_WEBHOOK_SECRET;
app.post('/webhooks/mapps', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('X-Mapps-Signature') ?? '';
const parts = Object.fromEntries(header.split(',').map((pair) => pair.split('=')));
const timestamp = Number(parts.t);
const body = req.body.toString('utf8');
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.${body}`).digest();
const received = Buffer.from(parts.v1 ?? '', 'hex');
const valid = received.length === expected.length && crypto.timingSafeEqual(received, expected);
if (!valid || Math.abs(Date.now() / 1000 - timestamp) > 300) {
return res.sendStatus(400);
}
const event = JSON.parse(body);
if (event.type === 'link.clicked') {
// Store event.data.click ...
}
res.sendStatus(200);
});
app.listen(3000);
Responses and retries
- Respond with any 2xx status within 10 seconds. Do slow work in a background job after you respond.
- Redirects are not followed, so use the final URL.
- The URL must be public
https. Private and local network addresses are refused. - If your endpoint times out or returns anything other than 2xx, we retry up to five more times: after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. Each attempt has a new timestamp and signature.
- The same event can arrive more than once, and events can arrive out of order. Use
X-Mapps-Delivery, the eventid, to ignore duplicates, andcreated_atto order them. - After 20 failed attempts in a row, the endpoint is turned off and nothing more is sent to it. Fix the problem, then turn it back on in the dashboard.
Zapier, Make and n8n
You can connect mapps.one to thousands of apps without running a server.
Zapier
- Create a Zap with Webhooks by Zapier as the trigger and choose Catch Hook.
- Copy the webhook URL Zapier gives you.
- In mapps.one, add an endpoint with that URL and pick the events you want, for example
app.status_changedoranalytics.daily_summary. - Choose Send test event in mapps.one, then Test trigger in Zapier. The fields from the payload, such as
data.app.name, can now be used in the next steps.
Examples: post a Slack message when an app is paused, add each day's clicks to a Google Sheet, or email a client when their domain is verified.
Make and n8n
In Make, use the Webhooks → Custom webhook trigger. In n8n, use the Webhook node with the POST method. Then add the URL as an endpoint in mapps.one as above.
Building an integration
If you are building a mapps.one integration for others, subscribe with the API when a user connects (POST /api/v1/webhooks), unsubscribe when they disconnect (DELETE /api/v1/webhooks/{id}), use GET /api/v1/events for the event picker, and use GET /api/v1/workspace to test the connection. See the API reference.