Series 6 — Part 9 of 10

When a lawyer dictates a voice note that creates a workspace task, the link between the audio and the resulting record matters for audit purposes. This article covers storing wa_message_id on created records, PHP globals for cross-cutting webhook context, and the workspace UI mic badge.

The Traceability Problem

When a voice note says "remind me about the Sharma hearing next Tuesday", the WhatsApp AI agent creates a workspace reminder. Without traceability, that reminder is an orphan — there is no way to retrieve the original audio, verify the transcript, or audit whether the reminder was created correctly.

Traceability requires: the wa_message_id of the voice note, the wa_file_id (Media API ID), and the transcript — stored on the created record.

PHP Globals for Cross-Cutting Webhook Context

// Set once in the webhook handler, before any handlers are called
class WebhookContext
{
    private static array $context = [];

    public static function set(array $data): void
    {
        self::$context = $data;
    }

    public static function getWaMsgId(): ?string
    {
        return self::$context['wa_msg_id'] ?? null;
    }

    public static function getWaFileId(): ?string
    {
        return self::$context['wa_file_id'] ?? null;
    }

    public static function getTranscript(): ?string
    {
        return self::$context['transcript'] ?? null;
    }
}

// In the webhook entry point:
WebhookContext::set([
    'wa_msg_id'  => $incomingMessage['id'],
    'wa_file_id' => $incomingMessage['audio']['id'] ?? null,
    'transcript' => null,  // filled after Whisper runs
]);

// After transcription:
WebhookContext::set([...WebhookContext::getAll(), 'transcript' => $transcribedText]);

Storing Traceability on Created Records

class AddNoteHandler
{
    public function handle(string $text, PersonaInterface $persona, PDO $pdo): string
    {
        $pdo->prepare(
            'INSERT INTO workspace_items
               (team_id, item_type, title, body, created_by, source_type, wa_message_id, transcript)
             VALUES (?, \'note\', ?, ?, ?, \'voice_note\', ?, ?)'
        )->execute([
            $persona->getTeamId(),
            $this->extractTitle($text),
            $text,
            $persona->getUserId(),
            WebhookContext::getWaMsgId(),
            WebhookContext::getTranscript(),
        ]);

        return "Note saved. You can review it in the workspace with the original voice recording.";
    }
}

What to Watch For

  • Media URL expiry — Meta media URLs expire after 5 minutes. If you need to retrieve the original audio later, you must download it at webhook time and store it locally, not just store the media_id URL.
  • Transcript privacy — Transcripts contain legal content that may be privileged. Store them with the same access controls as the workspace items they relate to.
  • Handler independence — Not every created record comes from a voice note. Handlers must work correctly when getWaMsgId() returns null (text messages).