Skip to main content
Webhooks let you receive outreach engagement events from Topo the instant they occur. When a tracked event happens in your workspace — a contact replies, a hot lead is created, a meeting is booked — Topo sends an HTTP POST to the URL you register, with a signed JSON payload describing the event. A webhook subscription defines which events you want to receive and where to receive them. You can optionally scope a subscription to events from specific sequence templates, keeping your integration focused on the campaigns that matter. Every delivery is signed with an HMAC-SHA256 signature so you can verify that requests genuinely originate from Topo.
Reading subscriptions requires the webhooks:read scope. Creating, updating, deleting, and testing subscriptions requires the webhooks:write scope.

The webhook subscription object

Every webhook subscription has the following fields. The secret field is the exception — it is only present in the create response and is never returned again.
id
string (UUID)
required
Unique identifier for this subscription.
name
string
required
Human-readable label for the subscription (1–255 characters).
url
string
required
The HTTPS URL Topo POSTs events to.
description
string | null
Optional free-form description.
event_types
array of strings
required
The event types this subscription receives. See Event types.
sequence_template_ids
array of UUIDs | null
When set, Topo only delivers events from those sequence templates. null means all templates.
status
string
required
Lifecycle status of the subscription. One of ACTIVE, PAUSED, DISABLED. See Subscription status.
secret_prefix
string
required
The first few characters of the signing secret, safe to display. Use this to identify which secret is in use without exposing it.
created_at
string (ISO 8601)
required
When the subscription was created.
updated_at
string (ISO 8601)
required
When the subscription was last modified.

Subscription status

StatusBehaviour
ACTIVETopo delivers matching events to the endpoint.
PAUSEDDelivery is temporarily suspended. Events that arrive while paused are not queued — resume the subscription to start receiving new events again.
DISABLEDThe subscription has been administratively disabled. Set status to ACTIVE via PATCH to re-enable it.

Event types

Each subscription declares which event types it wants to receive. The type field in every delivered payload is the discriminator.
type valueWhat it means
message.sentAn email or LinkedIn message was sent to the contact.
message.openedThe contact opened an email.
message.link_clickedThe contact clicked a tracked link in an email.
message.repliedThe contact replied to a message.
invitation.sentA LinkedIn connection request was sent.
invitation.acceptedThe contact accepted a LinkedIn connection request.
hot_lead.createdTopo’s AI flagged this contact as a hot lead.
meeting.createdA meeting was booked with the contact.
sequence.createdA contact was enrolled into a sequence.
sequence.pausedAn active sequence was paused.
sequence.resumedA paused sequence was resumed.
sequence.stoppedThe sequence was stopped early.
sequence.completedThe sequence ran to completion.
task.createdA manual task was created for the contact.
task.completedA manual task was marked complete.
task.skippedA manual task was skipped.
Topo may add new event types as additive changes within /v1. Your endpoint must accept and ignore unknown type values — do not return a non-2xx status just because you don’t recognise an event type.

Webhook payload structure

Every delivery is an HTTP POST with a JSON body. The payload is the same discriminated OutboundWebhookEventPayload object surfaced by the Activities API.
{
  "type": "hot_lead.created",
  "organization_id": "01954b2e-0000-7000-aaaa-111111111111",
  "person_id": "01954b2e-1111-7000-bbbb-222222222222",
  "sequence_id": "01954b2e-2222-7000-cccc-333333333333",
  "sequence_template_id": "01954b2e-3333-7000-dddd-444444444444",
  "resource_type": "HOT_LEAD",
  "channel": "EMAIL",
  "message_id": null,
  "hot_lead_id": "01954b2e-5555-7000-ffff-666666666666",
  "calendar_event_id": null,
  "task_id": null
}
All field names are snake_case. See Activities — shared payload fields for the full field reference.

Delivery headers

Every POST Topo sends includes these headers:
HeaderDescription
Content-Typeapplication/json
X-Topo-SignatureHMAC-SHA256 hex digest of the raw request body, prefixed with sha256=.
X-Topo-Event-TypeThe type value of the payload (e.g. hot_lead.created).

Verifying signatures

Every delivery is signed. You should reject requests that fail signature verification to prevent spoofed events from affecting your system. The signature is computed as:
HMAC-SHA256(key=<your_signing_secret>, message=<raw_request_body>)
Compare the result (hex-encoded, prefixed with sha256=) against the X-Topo-Signature header. Always compare using a constant-time equality function to prevent timing attacks.
Always verify the signature before acting on a payload. Do not trust the organization_id in the body without first confirming the signature is valid.
import hashlib
import hmac

def verify_topo_signature(
    raw_body: bytes,
    signature_header: str,
    secret: str,
) -> bool:
    """Return True if the request signature is valid."""
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)


# Flask example
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_••••••••••••••••"

@app.route("/topo-events", methods=["POST"])
def topo_webhook():
    signature = request.headers.get("X-Topo-Signature", "")
    if not verify_topo_signature(request.get_data(), signature, WEBHOOK_SECRET):
        abort(401)

    payload = request.get_json()
    event_type = payload["type"]
    # handle event_type ...
    return "", 204
Make your endpoint idempotent. In rare cases Topo may deliver the same event more than once (e.g. after a network timeout). Use the activity’s id — available via the Activities API — to deduplicate if needed.

List webhook subscriptions

GET /v1/webhooks
Returns a paginated list of all webhook subscriptions in the workspace.

Query parameters

page
integer
default:"1"
Page number. Must be ≥ 1.
size
integer
default:"10"
Items per page. Between 1 and 100.

Example

curl -G https://api.topo.io/v1/webhooks \
  -H "Authorization: Bearer topo_live_sk_••••••••••••••••" \
  -d page=1 \
  -d size=20

Create a webhook subscription

POST /v1/webhooks
Creates a new webhook subscription. Returns 201 Created with the subscription object, including the signing secret in a secret field.
The secret field is returned only in the creation response and is never exposed again. Store it securely (e.g. in your secrets manager) before discarding the response. If you lose it, delete the subscription and create a new one.

Request body

name
string
required
A human-readable label for the subscription. 1–255 characters.
url
string
required
The HTTPS URL Topo should POST events to. Must be a valid HTTP or HTTPS URL.
description
string
Optional free-form description. Up to 1,000 characters.
event_types
array of strings
required
One or more event type values this subscription should receive. Must contain at least one entry. See Event types.
sequence_template_ids
array of UUIDs
When provided, only events from these sequence templates are delivered to this subscription. Omit or pass null to receive events from all templates.

Response fields (creation only)

The creation response includes all subscription object fields plus:
secret
string
required
The full signing secret for this subscription. Use this to verify delivery signatures. Returned only once — store it now.

Example — create a subscription for hot leads and replies

curl -X POST https://api.topo.io/v1/webhooks \
  -H "Authorization: Bearer topo_live_sk_••••••••••••••••" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CRM hot-lead sync",
    "url": "https://integrations.example.com/topo/events",
    "description": "Push hot leads and replies to our CRM pipeline.",
    "event_types": [
      "hot_lead.created",
      "message.replied",
      "meeting.created"
    ],
    "sequence_template_ids": [
      "01954b2e-3333-7000-dddd-444444444444",
      "01954b2e-6666-7000-eeee-777777777777"
    ]
  }'
{
  "id": "01954b30-aaaa-7000-9999-000000000001",
  "name": "CRM hot-lead sync",
  "url": "https://integrations.example.com/topo/events",
  "description": "Push hot leads and replies to our CRM pipeline.",
  "event_types": ["hot_lead.created", "message.replied", "meeting.created"],
  "sequence_template_ids": [
    "01954b2e-3333-7000-dddd-444444444444",
    "01954b2e-6666-7000-eeee-777777777777"
  ],
  "status": "ACTIVE",
  "secret_prefix": "whsec_A1B2C",
  "secret": "whsec_A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0",
  "created_at": "2025-03-14T10:00:00.000Z",
  "updated_at": "2025-03-14T10:00:00.000Z"
}

Get a webhook subscription

GET /v1/webhooks/{id}
Fetches a single subscription by ID. The secret is not included in this response — only secret_prefix.

Path parameters

id
string (UUID)
required
The unique identifier of the subscription.

Example

curl https://api.topo.io/v1/webhooks/01954b30-aaaa-7000-9999-000000000001 \
  -H "Authorization: Bearer topo_live_sk_••••••••••••••••"

Update a webhook subscription

PATCH /v1/webhooks/{id}
Updates one or more mutable fields of a subscription. All fields are optional — omit any field you do not wish to change. Returns the updated subscription object.

Path parameters

id
string (UUID)
required
The unique identifier of the subscription to update.

Request body

name
string
New name. 1–255 characters.
url
string
New delivery URL.
description
string | null
New description. Pass null to clear an existing description.
event_types
array of strings
Replace the event type filter. Must contain at least one entry. Do not pass null — omit the field to leave it unchanged.
sequence_template_ids
array of UUIDs | null
Replace the sequence template scope. Pass null to remove scoping and receive events from all templates.
status
string
New lifecycle status. One of ACTIVE, PAUSED, DISABLED. Do not pass null — omit the field to leave it unchanged.

Example — pause a subscription

curl -X PATCH https://api.topo.io/v1/webhooks/01954b30-aaaa-7000-9999-000000000001 \
  -H "Authorization: Bearer topo_live_sk_••••••••••••••••" \
  -H "Content-Type: application/json" \
  -d '{ "status": "PAUSED" }'

Delete a webhook subscription

DELETE /v1/webhooks/{id}
Permanently deletes a subscription. Returns 204 No Content on success. This action cannot be undone — any endpoint that relied on this subscription will stop receiving events immediately.

Path parameters

id
string (UUID)
required
The unique identifier of the subscription to delete.

Example

curl -X DELETE https://api.topo.io/v1/webhooks/01954b30-aaaa-7000-9999-000000000001 \
  -H "Authorization: Bearer topo_live_sk_••••••••••••••••"

Send a test delivery

POST /v1/webhooks/{id}/test
Sends a synthetic test event to the subscription’s endpoint. Use this to verify that your endpoint is reachable and correctly processing Topo payloads before relying on live traffic. The test payload uses a realistic-looking event with placeholder UUIDs. The delivery is signed with the subscription’s real secret, so your signature verification logic is exercised end-to-end.

Path parameters

id
string (UUID)
required
The unique identifier of the subscription to test.

Response

delivered
boolean
required
true if your endpoint responded with a 2xx status code, false otherwise.
error
string | null
A human-readable description of the failure when delivered is false. null on success.

Example

curl -X POST https://api.topo.io/v1/webhooks/01954b30-aaaa-7000-9999-000000000001/test \
  -H "Authorization: Bearer topo_live_sk_••••••••••••••••"
{
  "delivered": true,
  "error": null
}
{
  "delivered": false,
  "error": "Connection refused: https://integrations.example.com/topo/events"
}

Sequence template scoping

By default a subscription receives events from every sequence template in your workspace. If you want to isolate events from a particular campaign — for example, routing events from your enterprise outbound template to a different CRM pipeline — set sequence_template_ids when creating or updating the subscription. A subscription with sequence_template_ids will only receive events where payload.sequence_template_id is in that list. Events from all other templates are silently ignored for that subscription. To remove scoping and receive events from all templates again, PATCH the subscription with "sequence_template_ids": null.

Webhook lifecycle

create (POST /v1/webhooks)


ACTIVE ─── events delivered ──▶ PAUSED (PATCH status=PAUSED)
   │                               │
   │                               └──▶ ACTIVE (PATCH status=ACTIVE)

   └──▶ DISABLED (administratively disabled)

            └──▶ ACTIVE (PATCH status=ACTIVE)
Topo’s API stability policy guarantees the subscription object shape, pagination envelope, and event type discriminator values are frozen within /v1. New event types may be added additively — your integration should ignore unknown type values rather than treating them as errors.