REST API · v1

The Jounce API

Manage your audience from your own code — subscribers, segments, custom fields, and signed webhooks. Base URL https://api.jounce.com/v1 · machine spec at openapi.json.

Quickstart

Create a key in Jounce under Account → Integrations → API, then:

curl https://api.jounce.com/v1/me \
  -H "Authorization: Bearer jk_live_…"
curl -X POST https://api.jounce.com/v1/subscribers \
  -H "Authorization: Bearer jk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"email":"jane@example.com","first_name":"Jane","segment_ids":["SEGMENT_ID"]}'

Authentication

Conventions

Endpoints

MethodPath
GET /v1/meThe account + key behind your credentials
GET /v1/subscribersList subscribers — filter by status, segment_id, or exact email; cursor pagination
POST /v1/subscribersCreate or update a subscriber by email
POST /v1/subscribers/batchCreate or update up to 100 subscribers
GET /v1/subscribers/{id_or_email}One subscriber
POST /v1/subscribers/{id_or_email}/segmentsAdd to segments
DELETE /v1/subscribers/{id_or_email}/segmentsRemove from segments
POST /v1/subscribers/{id_or_email}/unsubscribeUnsubscribe from all sends — works even for addresses not in Jounce yet
GET /v1/segmentsList segments
POST /v1/segmentsCreate a segment
GET /v1/segments/{id}One segment
GET /v1/custom-fieldsList the custom-field registry
POST /v1/custom-fieldsRegister a custom field
GET /v1/formsList forms
GET /v1/forms/{id}One form
GET /v1/automationsList automations (read-only)
GET /v1/webhooksList webhook endpoints
POST /v1/webhooksCreate a webhook endpoint — the secret is returned once
GET /v1/webhooks/{id}One webhook endpoint
PUT /v1/webhooks/{id}Update a webhook endpoint (re-enable resets the failure streak)
DELETE /v1/webhooks/{id}Delete a webhook endpoint

Subscribers — how writes behave

POST is an upsert by email (safe to retry). Status is one of subscribed · pending · unsubscribed · bounced · complained.

{
  "id": "jane@example.com",
  "email": "jane@example.com",
  "status": "subscribed",
  "source": "api",
  "first_name": "Jane",
  "last_name": "",
  "segments": [{ "id": "SEGMENT_ID", "name": "Customers" }],
  "custom_fields": { "company": "Acme" },
  "consent_at": "2026-07-22T10:00:00.000Z",
  "created_at": "2026-07-22T10:00:00.000Z",
  "updated_at": null
}

Adding a subscriber to a segment can enroll them in any automation that watches that segment — the same engine and safety gates as in-app adds. There is no separate enrolment endpoint; segments are the door.

Webhooks

Register an https endpoint and pick events. Deliveries are signed, retried (5 attempts with backoff over ~6 hours), and an endpoint that fails 10 straight deliveries is switched off — flip disabled back to re-arm it.

Event
subscriber.createdA subscriber was created — any source (bulk imports send one summary instead)
subscriber.updatedProfile or custom fields changed
subscriber.unsubscribedThey opted out — sync this into your other tools
subscriber.bouncedTheir mailbox hard-bounced
subscriber.complainedThey marked a send as spam
subscriber.added_to_segmentJoined a segment (includes segments set at creation)
subscriber.removed_from_segmentLeft a segment
list.importedA CSV import finished — one summary with counts, not one event per row

Payload: { event_id, event_name, event_time, webhook_id, subscriber | list, segment? }. Bulk imports deliberately do not fire per-row events.

Verify the signature

Every delivery carries Jounce-Signature: t=<unix>,v1=<hmac> — HMAC-SHA256 of t + "." + rawBody with your endpoint's secret (shown once at creation):

import crypto from 'node:crypto'

// header: "t=1721650000,v1=6c2e…"  rawBody: the EXACT request bytes
export function verifyJounceSignature(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false // 5-min replay window
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ''))
}

Migrating from another platform?

The request shapes here follow the field names most email APIs already use (first_name, custom_fields, segment_ids, double_optin…), and HTTP Basic auth works as-is — most existing integrations port by swapping the base URL and key. Statuses use Jounce's names, and pagination is cursor-based.

Jounce API v1 · openapi.json · Manage keys in Jounce → Integrations → API