Not every tool in your stack will have a native Stairoids integration — and that’s fine. The Stairoids REST API gives you a direct path to push signals in from any source and receive action triggers out via webhooks. Whether you’re connecting an internal data warehouse, a niche sales tool, or wiring things together with a no-code platform like n8n, Make, or Zapier, the Custom API integration is how you do it.
Two integration patterns
Use the POST /v1/signals endpoint to send a signal into Stairoids from any tool, script, or automation platform. This is the right pattern when your tool generates an event you want Stairoids to act on — for example, a product usage event, a form submission from a tool not natively integrated, or a custom intent signal from your data warehouse.When to use this pattern:
- Your tool supports outgoing webhooks or HTTP requests
- You’re using n8n, Make, or Zapier to relay events from a third-party tool
- You’re writing a custom script or backend service that generates events
Authentication uses a Bearer token. Generate your API key under Settings → API Keys → Create API Key. Configure an outgoing automation in Stairoids with action type Webhook. When the automation fires, Stairoids sends a POST request to your endpoint with the full signal and contact context. This is the right pattern when you want a non-native tool to respond to something that happened inside Stairoids.When to use this pattern:
- You want to trigger an action in a tool Stairoids doesn’t natively support
- You’re building a custom integration that needs to receive structured event data
- You want to log Stairoids automation events into your own data store
Stairoids signs every outgoing webhook with an HMAC-SHA256 signature so you can verify the request is genuine.
Example 1 — Push a signal in
Request
Send a POST request to https://api.stairoids.com/v1/signals with a JSON body describing the signal event.
curl -X POST https://api.stairoids.com/v1/signals \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "product_trial_started",
"source": "internal_app",
"account": {
"domain": "acmecorp.com",
"name": "Acme Corp"
},
"contact": {
"email": "jane.doe@acmecorp.com",
"first_name": "Jane",
"last_name": "Doe"
},
"properties": {
"plan": "pro_trial",
"trial_ends_at": "2024-09-15T00:00:00Z",
"invited_by": "sales_outreach"
}
}'
Request body fields
| Field | Type | Required | Description |
|---|
type | string | ✅ | The signal event type. Use any descriptive string for custom events. |
source | string | ✅ | An identifier for the system sending the signal (e.g., "internal_app", "zapier", "n8n"). |
account.domain | string | Recommended | The company domain. Used for account matching in Stairoids. |
account.name | string | — | The company name. Used if no existing account matches the domain. |
contact.email | string | Recommended | The contact’s email address. Used for contact matching. |
contact.first_name | string | — | Contact’s first name. |
contact.last_name | string | — | Contact’s last name. |
properties | object | — | Any additional key-value pairs relevant to the signal. Accessible in automation conditions and action templates. |
Response
A successful signal submission returns 201 Created:
{
"id": "sig_01j4k8m2n3p5q6r7s8t9u0v1",
"type": "product_trial_started",
"source": "internal_app",
"status": "received",
"account_id": "acc_01j4k8m2n3p5q6r7",
"contact_id": "con_01j4k8m2n3p5q6r8",
"created_at": "2024-09-01T14:32:00Z"
}
You can use any string value for the type field when sending custom signals — there is no required list. Use descriptive names that reflect the event in your system, such as "product_trial_started", "demo_requested", or "invoice_overdue". These type strings become available as trigger conditions in Stairoids automations immediately after the first signal of that type is received.
Example 2 — Receive a webhook action trigger
When a Stairoids automation fires an outgoing action of type Webhook, Stairoids sends a POST request to the URL you configure. Here’s the payload structure:
{
"event": "automation.action_triggered",
"automation_id": "auto_01j4k8m2n3p5q6r7s8",
"automation_name": "High-intent trial → notify sales",
"triggered_at": "2024-09-01T14:32:05Z",
"account": {
"id": "acc_01j4k8m2n3p5q6r7",
"name": "Acme Corp",
"domain": "acmecorp.com"
},
"contact": {
"id": "con_01j4k8m2n3p5q6r8",
"email": "jane.doe@acmecorp.com",
"first_name": "Jane",
"last_name": "Doe"
},
"signal": {
"id": "sig_01j4k8m2n3p5q6r7s8t9u0v1",
"type": "product_trial_started",
"source": "internal_app",
"properties": {
"plan": "pro_trial",
"trial_ends_at": "2024-09-15T00:00:00Z"
},
"created_at": "2024-09-01T14:32:00Z"
}
}
Verifying the HMAC-SHA256 signature
Every outgoing webhook includes an X-Stairoids-Signature header. Verify it to confirm the request genuinely came from Stairoids and hasn’t been tampered with.
The signature is computed as:
HMAC-SHA256(webhook_secret, raw_request_body)
Your webhook secret is shown once when you configure the Webhook action in Stairoids. Store it securely. Here’s how to verify it:
const crypto = require('crypto');
function verifyStairoidsSignature(rawBody, receivedSignature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
const trusted = Buffer.from(`sha256=${expectedSignature}`, 'ascii');
const received = Buffer.from(receivedSignature, 'ascii');
// Use timingSafeEqual to prevent timing attacks
return crypto.timingSafeEqual(trusted, received);
}
// In your Express route handler:
app.post('/webhook/stairoids', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-stairoids-signature'];
const isValid = verifyStairoidsSignature(req.body, signature, process.env.STAIROIDS_WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
const payload = JSON.parse(req.body);
// Handle payload...
res.status(200).send('OK');
});
import hmac
import hashlib
import os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get("STAIROIDS_WEBHOOK_SECRET")
def verify_signature(raw_body: bytes, received_signature: str) -> bool:
expected = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
raw_body,
hashlib.sha256
).hexdigest()
expected_header = f"sha256={expected}"
return hmac.compare_digest(expected_header, received_signature)
@app.route("/webhook/stairoids", methods=["POST"])
def stairoids_webhook():
signature = request.headers.get("X-Stairoids-Signature", "")
raw_body = request.get_data()
if not verify_signature(raw_body, signature):
abort(401)
payload = request.get_json(force=True)
# Handle payload...
return "OK", 200
Send a signal from code
const axios = require('axios');
async function sendStairoidsSignal(signalData) {
const response = await axios.post(
'https://api.stairoids.com/v1/signals',
signalData,
{
headers: {
'Authorization': `Bearer ${process.env.STAIROIDS_API_KEY}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
}
// Usage
sendStairoidsSignal({
type: 'demo_requested',
source: 'website',
account: { domain: 'acmecorp.com', name: 'Acme Corp' },
contact: { email: 'jane.doe@acmecorp.com', first_name: 'Jane', last_name: 'Doe' },
properties: { demo_type: 'product_tour', requested_at: new Date().toISOString() },
}).then(signal => console.log('Signal created:', signal.id));
If you’d rather not write code, use n8n or Make as a bridge between Stairoids and unsupported tools. Both platforms can send HTTP requests to POST /v1/signals and receive Stairoids outgoing webhooks using their built-in Webhook nodes — no custom code required. This lets you connect tools like Salesforce, Slack, Notion, Google Sheets, or virtually anything else with a few clicks.