ReactIn APIs

Track campaign events via API

Read the full outreach timeline of any campaign — invites sent, accepted, messages, replies and skips — programmatically, to sync outreach status into your CRM or build custom reporting.

3 min read

Every lead a campaign processes generates a campaign event — a timeline that records when the invite was sent, when it was accepted, when your messages went out, when the lead replied, or why they were skipped. The Campaign Events API exposes that timeline programmatically, so you can sync outreach status into your CRM, build custom reporting, or trigger workflows when a lead replies.

Prerequisites

Before you start, you need:

  • Your Organization ID — identifies which workspace you're querying.

  • Your Campaign ID — the campaign whose events you want to read. You'll find it in the campaign URL or campaign settings.

  • Your organization API key — a secret token used to authenticate every request. Find it, along with your Organization ID, under Settings → API Keys in the app.

⚠️ This endpoint only accepts your organization API key or a campaign-scoped key. A SmartList/list API key (the one auto-generated when you create a "ReactIn API" list) won't work here and returns 401 Unauthorized.

Keep your API key secret. Never commit it to source control or expose it in client-side code.

Base URL

All endpoints are served under your ReactIn instance and scoped to your organization:

https://app.reactin.io/api/{organizationId}/...

Authentication

Authenticate by sending your organization (or campaign-scoped) API key in the Authorization header. Both the Bearer prefix and a raw token are accepted:

Authorization: Bearer YOUR_API_KEY

A missing or invalid key returns 401 Unauthorized.

Endpoint

GET /api/{organizationId}/campaigns/{campaignId}/events

Returns the campaign's events, sorted newest-first by creation date (the most recently added leads first — not by latest activity), paginated and filterable by status and date.

Query parameters

ParameterTypeDefaultDescription
pagenumber1Page number (≥ 1)
pageSizenumber10Events per page (10–50)
statusstringFilter by step reached — matches events that have reached at least the given step (cumulative, not only the exact current step). Comma-separate several values to combine them (OR logic).
dateFromstring (ISO 8601)Only events created on or after this date
dateTostring (ISO 8601)Only events created on or before this date

Available status filter values. Each one matches events that have reached at least this step:

ValueMeaning
pendingQueued, no action taken yet
connection_sentConnection invite sent
connection_acceptedConnection invite accepted
message_sentFirst message sent
message_repliedThe lead replied
follow_up_sentA follow-up message was sent
skippedThe lead was skipped (see skippedReason)

ℹ️ The status filter is cumulative: ?status=connection_accepted also returns leads who have since been messaged or replied (their response status will then be message_sent, message_replied, etc.). ?status=skipped doesn't include leads skipped because they were already one of your connections, even though those still show "status": "skipped" in the response.

Example request

curl -s \
  -H "Authorization: Bearer YOUR_API_KEY" \
  "https://app.reactin.io/api/ORG_ID/campaigns/CAMPAIGN_ID/events?status=message_replied,connection_accepted&pageSize=25"

Example response

{
  "data": [
    {
      "id": "evt_123",
      "status": "message_replied",
      "createdAt": "2026-05-19T10:48:00.000Z",
      "connectionSentAt": "2026-05-15T09:00:00.000Z",
      "connectionAcceptedAt": "2026-05-16T14:20:00.000Z",
      "messageSentAt": "2026-05-17T08:30:00.000Z",
      "messageRepliedAt": "2026-05-19T10:48:00.000Z",
      "followUpMessageSentAt": null,
      "lead": {
        "firstName": "Jane",
        "lastName": "Doe",
        "email": "jane.doe@example.com",
        "linkedinProfileUrl": "https://www.linkedin.com/in/janedoe"
      },
      "skippedAt": null,
      "skippedReason": null
    }
  ],
  "page": 1,
  "nextPage": 2
}

Response fields

  • data[] — the events on this page.

    • status — the latest step the lead has reached (only the most advanced one: a lead who accepted then replied — or who replied after a follow-up was sent — shows message_replied, not follow_up_sent). This is the current step, whereas the status filter above is cumulative. To detect replies reliably, key off messageRepliedAt (or the message_replied filter) rather than assuming a follow-up step means the lead hasn't replied.

    • connectionSentAt, connectionAcceptedAt, messageSentAt, messageRepliedAt, followUpMessageSentAt — timestamps for each step, or null if it hasn't happened.

    • lead — the lead this event belongs to (firstName, lastName, email, linkedinProfileUrl).

    • skippedAt / skippedReason — set when the lead was skipped, otherwise null.

  • page — current page number.

  • nextPage — the next page number, or null when there are no more.

Rate limits

Requests are rate-limited per organization (30 requests / minute for reads). Every response includes the current limit state via X-RateLimit-* headers. When you exceed the limit you receive 429 Too Many Requests with a Retry-After header (in seconds) — back off and retry after that delay.

Error handling

StatusMeaningTypical cause
200OKRequest succeeded
400Bad RequestInvalid query parameter (bad status, malformed date, out-of-range pageSize)
401UnauthorizedMissing or invalid API key
404Not FoundOrganization or campaign doesn't exist in your workspace
429Too Many RequestsRate limit exceeded
500Internal Server ErrorSomething went wrong on our end — try again

Putting it together

A typical reporting loop looks like this:

  1. Poll GET /campaigns/{campaignId}/events?status=message_replied on a schedule to detect new replies.

  2. Track which events you've already handled by their id, and re-check them on each poll — a lead's status and timestamps evolve over time. Don't use dateFrom as an incremental cursor: it filters on when the event was created (when the lead entered the campaign), not on when the reply or acceptance happened, so a fresh reply on an older lead would be missed.

  3. Walk the pages via nextPage until it's null.

  4. Sync each event into your CRM or trigger your own workflow (alert, task, hand-off).