All documentation
Feature guide

Call Recordings

Upload and manage call recordings inside PRM Ninja. Optionally connect an external automation via webhook to ingest AI-summarized calls automatically.

Overview

Call Recordings lets you store phone call audio with summaries, transcripts, and contact links. There are two ways to add recordings:

Upload from PRM

Recommended

Upload audio files directly from the dashboard. Large files upload straight to S3 from your browser — no size limits through the PRM server.

Webhook automation

Optional. Send AI summaries from AWS Lambda or your phone system using a per-user webhook secret.

Upload from PRM

The primary way to add recordings is from inside PRM — no webhook or external setup required.

  1. Go to Call Recordings in the sidebar (/dashboard/call-recordings).
  2. Click Upload recording.
  3. Choose an audio file (MP3, WAV, M4A, WebM, OGG — up to 500 MB). PRM requests a presigned S3 URL, then your browser uploads the file directly to storage.
  4. Add optional details: title, date & time, call number (your SIM / line), receiver number (other party), contact, summary, and transcript.
  5. Click Upload. PRM saves the metadata and opens the recording detail page with audio playback.

Why this works for large files: The audio never passes through PRM's server. Only a small presigned URL request and a metadata save happen on PRM — the heavy file upload goes browser → S3 directly.

Manage recordings

Sort & search

Sort by call date, date added, title, call number, or receiver number. Search across titles, summaries, transcripts, phone numbers, and contact names. Filters sync to the URL so you can bookmark or share views.

Archive & restore

Archive recordings from the list menu or detail page. View archived items at /dashboard/call-recordings/archived. Restore anytime.

Assign contacts

Link a recording to any contact from the detail page. Assigned recordings also appear on the contact's profile.

Playback

Play audio inline on the detail page and from contact profiles. PRM serves audio via secure presigned S3 URLs.

How PRM stores audio

Whether you upload from the dashboard or via webhook automation, audio is stored in your configured S3 bucket under:

text
recordings/{your-user-id}/{optional-contact-id}/{id}-{filename}

PRM requires AWS S3 credentials on the server (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_S3_BUCKET). Your S3 bucket CORS configuration must allow PUT from your PRM domain for browser uploads to work.

Optional — external automation

The sections below are only needed if you want an external service (e.g. AWS Lambda call-summary) to push recordings into PRM automatically. If you upload manually from PRM, you can skip them.

Webhook integration

External automations POST call metadata to PRM after processing. Generate a per-user secret in Settings → Call Recordings Webhook.

http
POST https://www.ninjaprm.com.au/api/webhooks/call-recordings

Webhook setup

  1. Go to Settings → Call Recordings Webhook and click Generate secret.
  2. Copy the webhook URL, upload URL endpoint, and secret.
  3. Configure your automation to use the three-step flow below. Never send audio as base64 in the webhook JSON.

Webhook large files

Serverless hosts reject webhook bodies over ~4.5MB. Sending audioBase64 returns 413 Request Entity Too Large. Use the same pattern as PRM dashboard uploads: presigned S3 URL first, metadata webhook second.

http
POST https://www.ninjaprm.com.au/api/webhooks/call-recordings/upload-url
javascript
// Automation only — do not send audio in the webhook JSON body

// 1. Get presigned upload URL
const uploadRes = await fetch("https://www.ninjaprm.com.au/api/webhooks/call-recordings/upload-url", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_WEBHOOK_SECRET",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    externalId: call.id,
    filename: "recording.mp3",
    mimeType: "audio/mpeg",
  }),
});
const { uploadUrl, s3Key, mimeType } = await uploadRes.json();

// 2. Upload audio directly to S3
await fetch(uploadUrl, {
  method: "PUT",
  headers: { "Content-Type": mimeType },
  body: audioBuffer,
});

// 3. Send metadata only
await fetch("https://www.ninjaprm.com.au/api/webhooks/call-recordings", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_WEBHOOK_SECRET",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    externalId: call.id,
    s3Key,
    callNumber: call.callNumber,
    receiverNumber: call.receiverNumber,
    title: summary.title,
    summary: summary.text,
    transcript: call.transcript,
  }),
});

Webhook auth

http
Authorization: Bearer YOUR_WEBHOOK_SECRET

Each user has their own secret. With a valid bearer token, userId in the payload is optional.

Webhook payload

Metadata only — reference audio via s3Key, sourceS3Key, or audioUrl.

Phone numbers

Store both sides of the call separately — important when you use multiple SIM cards or lines:

  • callNumber — your line / SIM (aliases: call, from, fromNumber)
  • receiverNumber — the other party (aliases: receiver, to, toNumber)

Legacy caller / callerNumber fields map to callNumber.

json
{
  "externalId": "call-2026-06-18-045203",
  "callNumber": "+61400111222",
  "receiverNumber": "+61400999888",
  "occurredAt": "2026-06-18T04:52:21.000Z",
  "title": "Brief Disconnected Call Attempt",
  "sentiment": "Frustrated",
  "summary": "The caller experienced difficulty with the call connection...",
  "transcript": "Hello? Hello? ...",
  "s3Key": "recordings/your-user-id/call-id-recording.mp3"
}

Contact matching

PRM links recordings to contacts automatically when possible. This applies to webhook ingest and PRM uploads.

  1. Explicit contactId in the webhook payload or selected during PRM upload.
  2. Otherwise, match callNumber or receiverNumber against contact phone numbers (flexible formatting — spaces, country codes, etc.). If both numbers match different contacts, the receiver number wins.
  3. Otherwise unassigned — assign manually from the detail page.

Error codes

StatusMeaning
401Missing or invalid webhook bearer token
400Invalid payload or missing audio reference
404User not found (webhook)
413Webhook body too large — use presigned S3 upload, not base64
500S3 or server error