Developer Guide · Integration

Developer Documentation

Everything you need to integrate Markt into your website, mobile app, or backend. From visitor tracking to transactional emails, push notifications, webhooks, and conversion tracking.

JavaScript SDK

Drop in a script tag to track page views, custom events, and identify visitors on your website.

REST API

Manage contacts, campaigns, and trigger transactional emails from any language or platform.

Webhooks

Receive real time notifications when emails are sent, opened, clicked, bounced, or unsubscribed.

Push Notifications

Send push notifications to iOS, Android, and Web browsers via APNs, FCM, and VAPID.

Conversion Tracking

Send server side conversion events to ad platforms like Meta via the Conversions API (CAPI).

Transactional Email

Trigger event driven emails with dynamic data, merge tags, and time limited expiring links.

Authentication

Markt provides two authentication methods depending on your integration type. Client side tracking uses a Site Token, while server side operations use an API Token.

Site Token

Site Tokens are used for client side tracking (JavaScript SDK, mobile tracking). They are safe to expose in browser code because they can only submit tracking events and identify visitors. They cannot read or modify your data.

Pass the token via the X-Site-Token header or the site_token query parameter:

// Header
X-Site-Token: YOUR_SITE_TOKEN

// Query parameter (used by sendBeacon)
POST /api/v1/t?site_token=YOUR_SITE_TOKEN

Find your Site Token in Team Settings. Each team has a unique token.

API Token (Sanctum)

API Tokens are used for server side operations: managing contacts, controlling campaigns, triggering transactional emails, sending conversion events, and registering push devices. Include the token in the Authorization header:

Authorization: Bearer YOUR_API_TOKEN

Generate API tokens from your team settings in the dashboard. Tokens are scoped to your team.

JavaScript SDK

The Markt JavaScript SDK is a lightweight tracking library you embed on your website. It automatically tracks page views, supports custom events and visitor identification, handles SPA navigation, and batches requests for optimal performance.

Installation

Add the following snippet to your website, ideally before the closing </body> tag. Replace YOUR_SITE_TOKEN with your actual Site Token.

<script>
  window.markt = [];
  window.markt.SITE_TOKEN = 'YOUR_SITE_TOKEN';
  window.markt.API_HOST = 'https://your-domain.com';
</script>
<script src="https://your-domain.com/js/markt.js" defer></script>

The SDK initializes automatically. Before the script loads, you can queue calls using the array stub syntax:

// Queue calls before SDK loads
window.markt = window.markt || [];
window.markt.push(['track', 'signup_started', { plan: 'pro' }]);
window.markt.push(['identify', '[email protected]', { first_name: 'John' }]);

Methods

Method Description
markt.page(url?, title?, referrer?) Track a page view. Called automatically on load and SPA navigation.
markt.track(eventName, properties?) Track a custom event with optional properties.
markt.identify(email, traits?) Identify the current visitor as a known contact.
markt.getVisitorId() Returns the current visitor UUID.
markt.flush() Manually flush the event buffer immediately.

Page View

// Automatic (called on page load and navigation)
// No action needed unless you want to manually trigger

// Manual call with custom values
markt.page('https://example.com/products', 'Products Page', 'https://google.com');

Custom Event

// Track a custom event
markt.track('added_to_cart', {
  product_id: 'SKU-123',
  product_name: 'Blue T-Shirt',
  price: 29.99,
  currency: 'USD'
});

// Track without properties
markt.track('newsletter_popup_shown');

Identify

// After login or form submission
markt.identify('[email protected]', {
  first_name: 'John',
  last_name: 'Doe',
  company: 'Acme Inc',
  title: 'CTO',
  phone: '+1234567890',
  website: 'https://acme.com'
});

When identify() is called, all previous anonymous page views and events are retroactively linked to the contact. The SDK flushes any pending events before sending the identify call.

SPA Support

The SDK automatically detects single page application navigation by monkey patching history.pushState, history.replaceState, and listening to the popstate event. Each navigation triggers a page view event automatically. No additional configuration is required for frameworks like React, Vue, Next.js, or Nuxt.

Automatic Behaviors

  • Auto page view on initial load
  • Auto page view on pushState and replaceState navigation
  • Event batching: flushes every 2 seconds or when 10 events are buffered
  • Uses sendBeacon with fetch fallback for reliable delivery
  • Flushes pending events on page hide and visibility change
  • Visitor ID persisted in cookie (_markt_vid, 365 days) and localStorage
  • Session ID stored in sessionStorage (_markt_sid)

Tracking API

The Tracking API is the underlying endpoint used by the JavaScript SDK. You can also call it directly from mobile apps, server side code, or any HTTP client. Authentication uses a Site Token.

Rate Limit (per IP)

100 requests per minute

Rate Limit (per token)

1,000 requests per minute

POST /api/v1/t

Batch track page views, custom events, and identify calls. Accepts 1 to 50 events per request.

Headers

Content-Type: application/json
X-Site-Token: YOUR_SITE_TOKEN

Request Body

Field Type Description
visitor_id required string (uuid) Unique visitor identifier
session_id string (uuid) Session identifier
events required array (1 to 50) Array of event objects
context object Device context: user_agent, screen, platform, language

Event Object

Field Type Description
type required string One of: page_view, event, identify
name string Event name (for type event)
url string Page URL (max 2048 characters)
title string Page title
referrer string Referrer URL
properties object Custom key value properties
timestamp integer Unix timestamp in seconds

Example Request

curl -X POST https://your-domain.com/api/v1/t \
  -H "Content-Type: application/json" \
  -H "X-Site-Token: YOUR_SITE_TOKEN" \
  -d '{
    "visitor_id": "550e8400-e29b-41d4-a716-446655440000",
    "session_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "events": [
      {
        "type": "page_view",
        "url": "https://example.com/pricing",
        "title": "Pricing",
        "referrer": "https://google.com",
        "timestamp": 1705315200
      },
      {
        "type": "event",
        "name": "plan_selected",
        "properties": { "plan": "pro", "billing": "annual" },
        "timestamp": 1705315210
      }
    ],
    "context": {
      "user_agent": "Mozilla/5.0...",
      "screen": "1920x1080",
      "platform": "web",
      "language": "en-US"
    }
  }'

Response (202)

{
  "message": "Events accepted."
}
POST /api/v1/t/identify

Link an anonymous visitor to a known contact by email address. All previous anonymous activities from this visitor will be retroactively associated with the contact.

Request Body

Field Type Description
visitor_id required string (uuid) The anonymous visitor ID to resolve
email required string Contact email address
traits object Optional: first_name, last_name, company, title, phone, website

Example Request

curl -X POST https://your-domain.com/api/v1/t/identify \
  -H "Content-Type: application/json" \
  -H "X-Site-Token: YOUR_SITE_TOKEN" \
  -d '{
    "visitor_id": "550e8400-e29b-41d4-a716-446655440000",
    "email": "[email protected]",
    "traits": {
      "first_name": "John",
      "last_name": "Doe",
      "company": "Acme Inc"
    }
  }'

Response (200)

{
  "message": "Contact identified.",
  "contact_id": 42
}

REST API Overview

The Markt REST API is a JSON based API authenticated with Sanctum Bearer tokens. All server side integrations use this API.

Base URL

https://your-domain.com/api/v1

Rate Limit

60 requests per minute per token

Format

JSON request and response bodies

Endpoints

Method Endpoint Description
GET /contacts List contacts (paginated)
POST /contacts Create a contact
GET /contacts/{id} Get a contact
PUT /contacts/{id} Update a contact
DELETE /contacts/{id} Delete a contact
GET /campaigns List campaigns
GET /campaigns/{id} Get campaign details
POST /campaigns/{id}/start Start a campaign
POST /campaigns/{id}/pause Pause a campaign
GET /campaigns/{id}/stats Get campaign statistics
GET /events List transactional events
GET /events/{slug} Get transactional event details
POST /events/{slug}/trigger Trigger a transactional email
POST /conversions Send a conversion event (CAPI)
POST /push-devices Register a push device
PUT /push-devices/{token} Update a push device
DELETE /push-devices/{token} Unregister a push device

Rate Limit Headers

  • X-RateLimit-Limit : Maximum requests allowed per window
  • X-RateLimit-Remaining : Remaining requests in the current window
  • X-RateLimit-Reset : Unix timestamp when the limit resets

Error Responses

Validation errors return a 422 status code with field level error messages:

{
  "message": "The given data was invalid.",
  "errors": {
    "email": ["The email field is required."],
    "first_name": ["The first name must be a string."]
  }
}

For complete endpoint documentation with all request fields and response examples, see the API Reference.

Transactional Emails

Transactional emails are event driven messages triggered by your application via the API. First, define an event and email template in the Markt dashboard. Then, trigger the event from your backend with dynamic data that gets merged into the template.

1. Define Event

Create a transactional event in the dashboard and link it to an email template.

2. Design Template

Use {{key}} merge tags in your template for dynamic content.

3. Trigger via API

Call the trigger endpoint with the recipient and data payload.

POST /api/v1/events/{slug}/trigger

Trigger a transactional email to a recipient with dynamic data. The event must be active.

Request Body

Field Type Description
to required string (email) Recipient email address
to_name string Recipient display name
data required object Key value pairs merged into the template
send_at string (datetime) Schedule for future delivery (must be in the future)

Example: Order Confirmation

curl -X POST https://your-domain.com/api/v1/events/order-confirmation/trigger \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[email protected]",
    "to_name": "Jane Smith",
    "data": {
      "customer_name": "Jane",
      "order_id": "ORD-2024-001",
      "order_total": "$149.99",
      "tracking_url": "https://shipping.example.com/track/ABC123"
    }
  }'

Response (202)

{
  "message": "Event accepted.",
  "log_id": 156
}

Example: Scheduled Password Reset

{
  "to": "[email protected]",
  "data": {
    "reset_url": "https://app.example.com/reset?token=abc123",
    "user_name": "John"
  },
  "send_at": "2026-03-01T09:00:00Z"
}

Merge Tags

Use double curly braces in your email template to define dynamic placeholders. When the event is triggered, the data object values replace matching tags.


Hi {{customer_name}},

Your order {{order_id}} has been confirmed.
Total: {{order_total}}

Track your shipment: {{tracking_url}}

Webhooks

Markt sends outgoing webhooks as HTTP POST requests to your configured endpoints whenever email and campaign events occur. Use webhooks to keep your CRM, database, or other systems in sync with Markt in real time.

HTTP POST

All webhooks are sent as POST requests with JSON payloads

HMAC SHA256

Every webhook includes a signature header for verification

3 Retries

Automatic retry with 30 second backoff on failure

Available Events

Event Description
email.sent Email was successfully sent from Markt
email.delivered Email was delivered to the recipient's mailbox
email.opened Recipient opened the email
email.clicked Recipient clicked a tracked link in the email
email.bounced Email bounced (hard or soft bounce)
email.complained Recipient marked the email as spam
email.unsubscribed Recipient unsubscribed from emails
campaign.started A campaign was started

Payload Format

All webhooks are sent as JSON with the following structure:

{
  "event": "email.opened",
  "payload": {
    "contact_id": 123,
    "campaign_id": 45,
    "email": "[email protected]"
  },
  "timestamp": "2026-01-15T10:15:30+00:00"
}

Headers included with every webhook:

  • Content-Type: application/json
  • X-Webhook-Signature : HMAC SHA256 signature of the request body

Signature Verification

Each webhook includes an X-Webhook-Signature header containing an HMAC SHA256 hash of the JSON request body, signed with your webhook secret. Always verify the signature to confirm the webhook came from Markt.

Node.js

const crypto = require('crypto');

function verifyWebhook(secret, signature, body) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(body))
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express.js example
app.post('/webhook', express.json(), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  if (!verifyWebhook(process.env.WEBHOOK_SECRET, signature, req.body)) {
    return res.status(401).send('Invalid signature');
  }
  console.log('Event:', req.body.event);
  console.log('Payload:', req.body.payload);
  res.status(200).send('OK');
});

PHP

function verifyWebhook(string $secret, string $signature, string $rawBody): bool {
    $expected = hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signature);
}

// Laravel example
$signature = $request->header('X-Webhook-Signature');
$rawBody = $request->getContent();

if (!verifyWebhook(config('services.markt.webhook_secret'), $signature, $rawBody)) {
    abort(401, 'Invalid signature');
}

$event = $request->input('event');
$payload = $request->input('payload');

Python

import hmac
import hashlib
import json

def verify_webhook(secret: str, signature: str, body: dict) -> bool:
    expected = hmac.new(
        secret.encode(),
        json.dumps(body, separators=(',', ':')).encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# Flask example
@app.route('/webhook', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    if not verify_webhook(WEBHOOK_SECRET, signature, request.json):
        return 'Invalid signature', 401
    event = request.json['event']
    payload = request.json['payload']
    return 'OK', 200

For detailed event payloads and more examples, see the Webhooks Reference.

Push Notifications

Send push notifications to iOS, Android, and Web browsers. Create a Push App in the Markt dashboard with your platform credentials, then use the API to register devices and link them to contacts.

iOS (APNs)

Apple Push Notification service with JWT authentication

Android (FCM)

Firebase Cloud Messaging v1 API

Web (VAPID)

Web Push protocol with VAPID key authentication

POST /api/v1/push-devices

Register a device token for push notifications. If a device with the same token already exists, it will be updated instead.

Request Body

Field Type Description
push_app_id required integer ID of the Push App created in the dashboard
device_token required string Platform specific device token (APNs token, FCM token, or Web Push subscription JSON)
contact_email string (email) Link device to a contact by email
device_name string Device name (e.g. "John's iPhone")
device_model string Device model (e.g. "iPhone 15 Pro")
os_version string OS version (e.g. "17.2")
app_version string Your app version (e.g. "2.1.0")

Additional Endpoints

Method Endpoint Description
PUT /push-devices/{token} Update device metadata or link to a contact
DELETE /push-devices/{token}?push_app_id=1 Unregister a device (marks as inactive)

iOS Integration (APNs)

To send push notifications to iOS devices:

  • 1. Create a Push App in the Markt dashboard with your APNs credentials (Key ID, Team ID, Auth Key .p8 file, Bundle ID)
  • 2. In your iOS app, request notification permissions and obtain the device token from didRegisterForRemoteNotificationsWithDeviceToken
  • 3. Send the device token to the Markt API via POST /api/v1/push-devices
  • 4. Optionally link the device to a contact by passing contact_email
// Swift example
func application(_ application: UIApplication,
  didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()

    var request = URLRequest(url: URL(string: "https://your-domain.com/api/v1/push-devices")!)
    request.httpMethod = "POST"
    request.setValue("Bearer \(apiToken)", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try? JSONEncoder().encode([
        "push_app_id": pushAppId,
        "device_token": token,
        "contact_email": userEmail,
        "device_model": UIDevice.current.model,
        "os_version": UIDevice.current.systemVersion
    ])
    URLSession.shared.dataTask(with: request).resume()
}

Android Integration (FCM)

To send push notifications to Android devices:

  • 1. Create a Push App in the Markt dashboard with your FCM credentials (Service Account JSON)
  • 2. In your Android app, integrate Firebase Cloud Messaging and obtain the device token from FirebaseMessaging.getInstance().getToken()
  • 3. Send the device token to the Markt API via POST /api/v1/push-devices
// Kotlin example
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
    val json = JSONObject().apply {
        put("push_app_id", pushAppId)
        put("device_token", token)
        put("contact_email", userEmail)
        put("device_model", Build.MODEL)
        put("os_version", Build.VERSION.RELEASE)
        put("app_version", BuildConfig.VERSION_NAME)
    }

    val request = Request.Builder()
        .url("https://your-domain.com/api/v1/push-devices")
        .addHeader("Authorization", "Bearer $apiToken")
        .post(json.toString().toRequestBody("application/json".toMediaType()))
        .build()

    OkHttpClient().newCall(request).enqueue(...)
}

Web Push Integration (VAPID)

To send push notifications to web browsers:

  • 1. Create a Push App in the Markt dashboard with platform set to "Web" (VAPID public and private keys will be generated)
  • 2. Register a service worker in your website and subscribe the user to push
  • 3. Send the subscription JSON as the device_token to the Markt API
// Browser JavaScript
async function subscribeToPush(pushAppId, vapidPublicKey) {
  const registration = await navigator.serviceWorker.ready;
  const subscription = await registration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
  });

  await fetch('https://your-domain.com/api/v1/push-devices', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + apiToken,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      push_app_id: pushAppId,
      device_token: JSON.stringify(subscription),
      contact_email: userEmail
    })
  });
}

Conversion Tracking (CAPI)

The Conversions API (CAPI) lets you send server side conversion events to ad platforms like Meta (Facebook/Instagram). Server side events are more reliable than browser pixels because they are not affected by ad blockers, cookie restrictions, or browser privacy settings.

POST /api/v1/conversions

Send a conversion event to the active ad integration. The event is queued and forwarded to the ad platform (e.g. Meta Conversions API).

Request Body

Field Type Description
event_name required string Standard or custom event name (e.g. Purchase, Lead, AddToCart)
event_id required string Unique identifier for deduplication (must be globally unique)
event_time required integer Unix timestamp of when the event occurred
event_source_url string (url) URL where the event occurred
user_data required object User matching parameters (see below)
custom_data object Event specific data (see below)

user_data Fields

Field Description
em Email address (SHA256 hashed, lowercase, trimmed)
ph Phone number (SHA256 hashed, digits only)
fn First name (SHA256 hashed, lowercase)
ln Last name (SHA256 hashed, lowercase)
fbc Facebook click ID (from _fbc cookie, not hashed)
fbp Facebook browser ID (from _fbp cookie, not hashed)
client_ip_address Client IP address (not hashed)
client_user_agent Client user agent string (not hashed)

custom_data Fields

Field Description
value Monetary value of the event (numeric)
currency ISO 4217 currency code (e.g. USD, EUR)
content_ids Array of product or content IDs
content_type Type of content (e.g. "product", "product_group")

Example: Purchase Event

curl -X POST https://your-domain.com/api/v1/conversions \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "Purchase",
    "event_id": "order-2024-001-abc123",
    "event_time": 1705315200,
    "event_source_url": "https://shop.example.com/checkout/success",
    "user_data": {
      "em": "a1b2c3d4e5f6...sha256hash",
      "ph": "b2c3d4e5f6a1...sha256hash",
      "fbc": "fb.1.1705315200.ABCdef",
      "fbp": "fb.1.1705315200.XYZabc",
      "client_ip_address": "203.0.113.50",
      "client_user_agent": "Mozilla/5.0..."
    },
    "custom_data": {
      "value": 149.99,
      "currency": "USD",
      "content_ids": ["SKU-001", "SKU-002"],
      "content_type": "product"
    }
  }'

Response (202)

{
  "message": "Event accepted.",
  "event_id": "order-2024-001-abc123"
}

Standard Event Names

Event Name Use Case
Purchase Completed purchase or transaction
Lead Lead generation (form submission, sign up)
AddToCart Item added to shopping cart
ViewContent Product or content page view
InitiateCheckout Checkout process started
CompleteRegistration User registration completed
Subscribe Subscription started

Event Deduplication

The event_id field must be globally unique and is used for deduplication when the same event is sent from both the browser pixel and the server side CAPI. Use the same event_id in both the client side Meta Pixel call and the server side CAPI request so ad platforms can deduplicate correctly.

// Generate a unique event ID to use in both pixel and CAPI
const eventId = 'evt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);

// Client side: Meta Pixel
fbq('track', 'Purchase', {value: 49.99, currency: 'USD'}, {eventID: eventId});

// Server side: Markt CAPI (send from your backend)
// POST /api/v1/conversions with the same event_id

Ad Pixel Integration

For maximum conversion tracking accuracy, use both the browser side ad pixel and the server side CAPI together. The browser pixel captures events from the user's browser, while CAPI provides a reliable server side signal that is not affected by ad blockers or browser privacy restrictions.

Browser Pixel (Client Side)

JavaScript snippet that fires events in the user's browser. Set up via the Ads > Pixel section in the dashboard. Subject to ad blockers.

CAPI (Server Side)

REST API endpoint that sends events from your backend. Not affected by ad blockers. Requires event_id for deduplication.

Recommended Setup

  • 1. Connect your Meta ad account in the Markt dashboard (Ads > Integrations)
  • 2. Get your pixel snippet from the Ads > Pixel page and add it to your website
  • 3. Generate a unique event_id for each conversion and pass it to both the pixel and CAPI
  • 4. From your backend, send the same event via POST /api/v1/conversions with the matching event_id

The ad platform will automatically deduplicate events that share the same event_id, so you will not be double counted.

Email Tracking

Markt automatically tracks email opens and link clicks. No integration is required from your side. This section explains how it works for transparency.

Open Tracking

A 1x1 transparent pixel is embedded in each email. When loaded, a page view is recorded.

Click Tracking

Links in emails are wrapped with tracking URLs. The click is recorded and the user is redirected to the original URL.

Expiring Links

Transactional emails can include time limited signed URLs that expire after a configured number of hours.

How It Works

URL Purpose
GET /t/o/{hash} Open tracking pixel. Returns a 1x1 transparent GIF and records the open event.
GET /t/c/{hash}?url=... Click tracking redirect. Records the click event and redirects to the original URL.
GET /t/e?signature=... Expiring link redirect. Validates the signed URL and expiry, then redirects.

These tracking URLs are automatically injected into campaign and transactional emails by Markt. Open and click events are also available via webhooks.

Platform Guides

Quick reference for integrating Markt based on your platform type.

Website

For traditional websites and SPAs (React, Vue, Next.js, Nuxt).

  • Install the JavaScript SDK for visitor tracking, page views, and custom events
  • Use markt.identify() after login or form submission to link visitors to contacts
  • Add the ad pixel snippet from the dashboard for conversion tracking
  • Set up webhooks to sync email events to your backend

Mobile App (iOS / Android)

For native mobile applications.

Backend (Any Language)

For server side integrations (Node.js, PHP, Python, Ruby, Go, etc.).

Best Practices

Identify visitors early

Call markt.identify() as soon as you know the visitor's email (after login, registration, or form submission). This ensures all prior anonymous activity is linked to the contact for better attribution and segmentation.

Use event_id for deduplication

When sending both browser pixel events and server side CAPI events, use the same event_id for both. Ad platforms will automatically deduplicate, preventing double counting.

Handle webhooks idempotently

Webhooks may be retried up to 3 times. Design your webhook handler to be idempotent by checking for duplicate events using the event data. Always return a 2xx status code quickly and process the event asynchronously.

Store secrets securely

Never expose your API token or webhook secret in client side code. API tokens should only be used in server side environments. Site Tokens are the only credentials safe for browser use.

Use scheduled sending wisely

For time sensitive transactional emails (password resets, verification codes), send them immediately without send_at. Use scheduled sending for non urgent notifications that should arrive at a specific time.

Hash user data for CAPI

Always SHA256 hash personal data (email, phone, names) before sending to the CAPI endpoint. The fbc, fbp, client_ip_address, and client_user_agent fields should not be hashed.

Respect rate limits

Monitor the X-RateLimit-Remaining header and implement exponential backoff when you receive a 429 response. The tracking API allows 100 requests per minute per IP and 1,000 per minute per token.

Ready to integrate?

Explore the full API reference and webhooks documentation for detailed endpoint information.