API & Webhook Integration

Last updated July 2026

Porter provides a REST API and a webhook system so you can integrate visitor management into your existing tools. Use the API to read and manage visitor data programmatically, and webhooks to receive real-time notifications the moment an event happens. API access and webhooks are available on plans that include them; if the feature is not on your plan, requests return an entitlement error.

Getting Your API Key

You need an API key to authenticate every request. Only organisation Owners and Admins can create keys.

  1. Go to Settings > API Keys.
  2. Click Create API Key.
  3. Give the key a descriptive name (for example "Production Integration" or "Zapier") so you can recognise where it is used, then click Create Key.
  4. Copy the key immediately. The full key is shown only once and cannot be recovered afterwards.
  5. Store it somewhere secure, such as a secrets manager or an environment variable. Never commit it to source control or expose it in client-side code.
Porter API Keys settings page listing existing keys with a Create API Key button
Settings > API Keys: create, name, and revoke keys. Only the key prefix is stored, so the full value is shown once.

The key list shows each key's prefix, its permissions, when it was last used, and any expiry. You can revoke a key at any time from the same page. Revoking is immediate and permanent: any integration using that key stops working straight away, so rotate keys by creating a new one before revoking the old.

Authentication

Send your API key with every request over HTTPS.

API Key Header

Include your key in the X-API-Key header:

X-API-Key: your_api_key_here

A missing or invalid key returns 401. Every response uses a consistent envelope: successful responses are { "success": true, "data": { ... } } and errors are { "success": false, "error": { "code", "message" } }.

Permissions (Scopes)

Each key carries one or more scopes, shown against the key in the list. Scopes are independent and enforced per endpoint:

  • read is required for all GET requests.
  • write is required to create or update records (POST / PATCH).
  • delete is required to delete records.

A key never gains a scope it was not granted, and calling an endpoint without the required scope returns 403 INSUFFICIENT_SCOPE. New keys default to read-only.

Rate Limiting

  • The limit is 100 requests per minute per API key.
  • Exceeding it returns a 429 Too Many Requests response.
  • The response includes a Retry-After header (seconds until you can retry), plus X-RateLimit-Limit and X-RateLimit-Remaining.
  • Always call the API over HTTPS.

Available Endpoints

All endpoints are under /api/v1 and are tenant-scoped to the organisation that owns the key.

MethodEndpointDescriptionScope
GET/api/v1/visitorsList visitors, with pagination, search, and filtersread
POST/api/v1/visitorsCreate a visitor recordwrite
GET/api/v1/visitors/:idGet a visitor and their recent visitsread
PATCH/api/v1/visitors/:idUpdate a visitor recordwrite
DELETE/api/v1/visitors/:idDelete a visitor recorddelete
GET/api/v1/visitsList visits; filter by location, status, type, and date rangeread
GET/api/v1/visits/:idGet a single visitread
PATCH/api/v1/visits/:idCheck a visit outwrite
GET/api/v1/locationsList active locationsread
GET/api/v1/locations/:idGet a single locationread
GET/api/v1/hostsList hosts (team members)read

List endpoints accept page and limit (up to 100) query parameters and return a pagination object alongside the results.

Webhook Setup

Webhooks let Porter push events to your server the moment they happen, so you do not have to poll the API. Porter sends an HTTP POST to the URL you register whenever a subscribed event fires.

Creating a Webhook

  1. Go to Settings > Webhooks and click Add Webhook.
  2. Enter the Endpoint URL where you want to receive events. It must be publicly reachable and use HTTPS.
  3. Optionally add a short description to remind yourself what the endpoint is for.
  4. Select at least one event to subscribe to.
  5. Click Create Webhook.
Porter Webhooks settings page showing a registered endpoint, its subscribed events, and controls to test, pause, and delete it
Settings > Webhooks: registered endpoints, subscribed events, and per-endpoint test, pause, and delete controls.

Each registered webhook can be paused and resumed. A paused webhook receives no events until you resume it.

Available Events

Subscribe to any combination of the following. The event identifier is sent in the payload and in the X-Porter-Event header.

  • VISIT_CHECKED_IN – a visitor checks in, at a kiosk or from the dashboard
  • VISIT_CHECKED_OUT – a visit is checked out (manually, by auto-checkout, or during an evacuation)
  • VISIT_PRE_REGISTERED – a visit is pre-registered or invited
  • VISITOR_CREATED – a new visitor record is created
  • CONTRACTOR_COMPLIANCE_CHANGED – a contractor's compliance status changes
  • EVACUATION_STARTED – evacuation mode is started
  • EVACUATION_ENDED – evacuation mode ends

Every delivery has the same envelope. The data fields vary by event type:

{
  "event": "VISIT_CHECKED_IN",
  "timestamp": "2026-03-01T09:32:00.000Z",
  "data": {
    "visitId": "visit_xyz789",
    "visitorId": "vis_def456",
    "locationId": "loc_ghi012"
  }
}

Requests also carry an X-Porter-Event header with the event name and a User-Agent of Porter-Webhook/1.0.

Testing a Webhook

On a registered webhook, click the Send test (paper plane) icon. Porter sends a sample payload to your URL using the webhook's first subscribed event, so you can confirm your endpoint receives and verifies deliveries before you depend on it. The test body is marked with "test": true.

Delivery Logs

Open a webhook from the list (the open-details icon) to see its delivery history. Each entry records the event, the HTTP response status your endpoint returned, the timestamp, and whether the delivery succeeded (a green or red dot). Expand any entry to inspect the exact payload Porter sent, the response body it received back, and the attempt count.

Screenshot of a webhook's delivery history (per-delivery status, payload, and response body) to be added.

Retries and Timeouts

  • If your endpoint returns a non-2xx status or times out, Porter retries automatically up to 3 times with increasing back-off: 1 minute, then 5 minutes, then 30 minutes (4 attempts in total).
  • You can also retry a failed delivery manually with the Retry button next to it in the delivery history.
  • Your endpoint must respond within 10 seconds. For slow work, acknowledge the request immediately and process it asynchronously.

Verifying Webhook Signatures

Every webhook request includes a signature so you can confirm it genuinely came from Porter and was not tampered with.

  • Each request carries an X-Porter-Signature header.
  • The value is an HMAC-SHA256 hash (hex) of the raw request body, computed with your webhook's signing secret.
  • The signing secret is shown only once, when you create the webhook. For security it is never displayed again. If you lose it, delete the endpoint and create a new one to get a fresh secret.
  • Always verify signatures in production to reject spoofed requests, and compare using a constant-time function.

Code Examples

Practical examples for calling the API and verifying a webhook signature. Results live under the data key of the response envelope.

cURL: List Visitors

curl -X GET https://portervisitors.com/api/v1/visitors \
  -H "X-API-Key: your_api_key_here"

A successful response looks like:

{
  "success": true,
  "data": {
    "visitors": [ /* visitor records */ ],
    "pagination": { "page": 1, "limit": 20, "total": 128, "totalPages": 7 }
  }
}

JavaScript: List Visitors

const res = await fetch("https://portervisitors.com/api/v1/visitors", {
  method: "GET",
  headers: { "X-API-Key": "your_api_key_here" },
});

const { data } = await res.json();
console.log(data.visitors);

Python: List Visitors

import requests

res = requests.get(
    "https://portervisitors.com/api/v1/visitors",
    headers={"X-API-Key": "your_api_key_here"},
)

body = res.json()
print(body["data"]["visitors"])

JavaScript: Verify a Webhook Signature

import crypto from "crypto";

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler (use the raw, unparsed request body):
const isValid = verifyWebhookSignature(
  rawBody,
  req.headers["x-porter-signature"],
  process.env.PORTER_WEBHOOK_SECRET
);

if (!isValid) {
  return res.status(401).json({ error: "Invalid signature" });
}

Python: Verify a Webhook Signature

import hmac
import hashlib

def verify_webhook_signature(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        body,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(signature, expected)

# In your webhook handler (use the raw request body):
is_valid = verify_webhook_signature(
    request.body,
    request.headers.get("X-Porter-Signature"),
    os.environ["PORTER_WEBHOOK_SECRET"],
)

if not is_valid:
    return JsonResponse({"error": "Invalid signature"}, status=401)

No-Code Integrations

You do not have to write code to connect Porter. The Integrations page lists the ready-made connectors and where each one is configured.

Porter Integrations page with cards for Slack, Microsoft Teams, a fire-alarm trigger, and webhooks
The Integrations directory links each connector to the screen where it is actually set up.
  • Slack and Microsoft Teams visitor-arrival notifications are set up per team member on the Notification settings page, using an incoming-webhook URL from your workspace.
  • A fire alarm or panic trigger (a wired relay, BMS, or panic app) can start an evacuation roll-call by POSTing to /api/integrations/alarm with an API key that has the write scope.
  • Because a webhook can POST to any HTTPS URL, you can point one at an automation platform such as Zapier or Make (paste their catch-hook URL as your endpoint) to fan Porter events out to thousands of apps without code.
Was this article helpful?