SDK@ear3/server

@ear3/server

Server SDK. Retrieve sessions, mint invite links, and verify webhook signatures. Requires Node 18+ (native fetch + node:crypto).

npm install @ear3/server

new Ear3(secretKey, options?)

import { Ear3 } from '@ear3/server'
 
const ear3 = new Ear3(process.env.EAR3_CONFIG_CLI_KEY!) // sk_live_… or sk_test_…

Options

OptionTypeNotes
baseUrlstringOverride Ear3 host. Falls back to EAR3_BASE_URL env, then https://app.ear3.ai
fetchOptionsRequestInitDefault fetch options applied to every request

The constructor throws if secretKey doesn’t start with sk_ — publishable keys belong in the browser SDK (@ear3/voice-interviewer), not here.


ear3.sessions.create(params)

const session = await ear3.sessions.create({
  interviewId: 'dpl_…',
  participantName: 'Olena K.', // dashboard display name (optional)
  participantExternalId: 'crm_42', // your own respondent id (optional)
  metadata: { userId: '42' },
})

Returns { sessionId, sessionUrl, expiresAt }. Useful for emailing session links, batch invitations, or pre-warming a session in a server component before iframing it.


ear3.sessions.retrieve(sessionId)

const session = await ear3.sessions.retrieve('inv_…')

Returns:

{
  id: string                  // same as the sessionId you passed
  interviewId: string
  status: 'PENDING' | 'SENT' | 'CLICKED' | 'STARTED' | 'COMPLETED' | 'EXPIRED'
  createdAt: string           // ISO
  clickedAt: string | null
  completedAt: string | null
  metadata: Record<string, unknown> | null  // what you passed in
  url: string | null          // /v2/responder/<hash> absolute URL
  expiresAt: string | null
  response: {
    id: string
    status: string
    transcriptKey: string | null
    summary: string | null
  } | null
}

Use this in your webhook handler if you need more than what event.data carries (e.g. the full transcript pointer).


Webhook event types

Ear3 emits exactly four webhook event types today. Subscribe to the ones you care about when you register the endpoint in the dashboard.

event.typeWhen it fires
interview.startedRespondent opened the link and the session began
interview.completedRespondent finished all questions
interview.failedVoice pipeline crashed mid-interview
session.expiredLink expired before the respondent reached the end

Handle unknown event.type values gracefully — new types may be added without a breaking-change release.

ear3.webhooks.constructEvent(rawBody, signature, secret, options?)

import { Ear3, SignatureVerificationError } from '@ear3/server'
 
try {
  const event = ear3.webhooks.constructEvent(
    rawBody,                   // BEFORE JSON.parse — see warning
    req.headers['ear3-signature'],
    process.env.EAR3_WEBHOOK_SECRET!,
  )
} catch (err) {
  if (err instanceof SignatureVerificationError) {
    return new Response('Invalid signature', { status: 400 })
  }
  throw err
}

Options

OptionTypeDefaultNotes
toleranceSecondsnumber300Reject timestamps outside this window

Throws SignatureVerificationError for:

  • Missing or malformed Ear3-Signature header
  • Computed HMAC doesn’t match any v1= in the header (constant-time)
  • Timestamp outside the tolerance window
  • rawBody isn’t valid JSON

🛑 Don’t JSON.parse(rawBody) before passing it in. Re-serializing changes whitespace, which changes the HMAC, which breaks the check. Use req.text() (Next.js App Router) or express.raw({ type: 'application/json' }).


Errors

  • Ear3Error — non-2xx response from the API. Has .status and .code.
  • SignatureVerificationError — see above.

Full error catalog is in the Reference → Errors page.


Built by Ear3 — voice interviews for any app.
⌘/