Developer Guide · Recipes

Recipe: education platform

A complete integration for an online academy or school: WhatsApp messages to parents on enrollment and results, student data in the inbox and the AI agent, and subscription activation from the chat.

This recipe connects a learning platform (LMS) to Inboxi so that parents get timely WhatsApp updates, support agents see the student's courses and grades next to every chat, and the AI agent can answer “is my son's subscription active?” on its own. It uses three building blocks: external data, event messaging and remote actions.

Architecture

LMS event
Signup, enrollment, exam graded
then
1. Push data
POST /external-data
then
2. Trigger event
POST /events/trigger
delivers
Parent on WhatsApp
Message + AI with context
The golden rule
Push the data first, then trigger the event. If the parent replies to the message, the agent and the AI already have the latest records.

Prerequisites

  • A Growth plan or higher and a connected WhatsApp Business number (WhatsApp setup).
  • An API key from Settings › Developer Hub › API Keys with the default permissions (read, write, external_data).
  • Events activated in Settings › Automated Messages with session messages and approved templates.
  • Parents' WhatsApp numbers stored in international format, e.g. 201016772111.

Data model

Use one source for your platform and one dataType per entity. Key every record to the parent's WhatsApp number, because that is who chats with you.

dataTypeexternalIdTypical data
parentparent_4521parent_name, email, relation
studentstudent_9871student_name, grade, school, status
enrollmentsenrollment_3341course_name, status, start_date, expiry_date
gradesgrade_exam101_9871exam_name, course_name, score, max_score
attendanceatt_2026-10-10_9871date, status

Pushing parent and student records also renames the matching chat, so agents see “Mohamed Ahmed” instead of a number.

What to send, and when

On your platformPush (external data)Then trigger (event)
New student registersparent + studentcustom.registered
Student enrolls in a courseenrollmentsenrollment.created
Payment activates a subscriptionenrollments (status: active)enrollment.activated
Subscription expiresenrollments (status: expired)enrollment.expired
Exam gradedgradescustom.exam_result
Daily attendanceattendance (bulk)-
Monthly reportgrades + attendancestudent.performance_report

Events starting with custom. are ones you create with New Custom Event; the rest are built in.

A small client

lib/inboxi.js
const BASE = 'https://inboxi.app/api/v1';

async function call(path, body) {
  const res = await fetch(BASE + path, {
    method: 'POST',
    headers: { 'X-API-Key': process.env.INBOXI_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
  const json = await res.json();
  if (!json.success) throw new Error(`${json.error}: ${json.message || ''}`);
  return json;
}

// Always international digits, no + and no 00
const intl = (phone) => phone.replace(/\D/g, '').replace(/^00/, '');

module.exports = {
  pushData: (r) => call('/external-data', { source: 'academy', ...r, contactIdentifier: intl(r.contactIdentifier) }),
  pushBulk: (dataType, records) => call('/external-data/bulk', {
    source: 'academy', dataType,
    records: records.map((r) => ({ ...r, contactIdentifier: intl(r.contactIdentifier) }))
  }),
  trigger: (event, phone, data) => call('/events/trigger', { event, phone: intl(phone), data })
};

Enrollment and grade handlers

javascript
const inboxi = require('./lib/inboxi');

async function onEnrollmentCreated({ enrollment, student, parent, course }) {
  await inboxi.pushData({
    dataType: 'enrollments',
    contactIdentifier: parent.whatsapp,
    externalId: `enrollment_${enrollment.id}`,
    data: {
      student_name: student.name,
      course_name: course.name,
      status: 'pending_payment',
      start_date: enrollment.startDate,
      course_link: `https://academy.example.com/courses/${course.id}`
    }
  });

  await inboxi.trigger('enrollment.created', parent.whatsapp, {
    student_name: student.name,
    course_name: course.name,
    access_link: `https://academy.example.com/courses/${course.id}`
  });
}

async function onExamGraded({ result, student, parent, exam }) {
  await inboxi.pushData({
    dataType: 'grades',
    contactIdentifier: parent.whatsapp,
    externalId: `grade_${exam.id}_${student.id}`,
    data: { student_name: student.name, exam_name: exam.name, course_name: exam.courseName,
            score: result.score, max_score: exam.maxScore }
  });

  await inboxi.trigger('custom.exam_result', parent.whatsapp, {
    student_name: student.name, exam_name: exam.name, score: result.score, total: exam.maxScore
  });
}

Run nightly attendance through pushBulk('attendance', rows) in batches of 500.

Activate subscriptions from the chat

When a parent sends a payment receipt on WhatsApp, the agent should be able to activate the course without leaving Inboxi. Register an action once:

json
POST /api/v1/actions
{
  "name": "activate_subscription",
  "label": "Activate subscription",
  "description": "Activate or deactivate a student's course subscription",
  "callbackUrl": "https://api.example.com/inboxi/actions/subscription",
  "category": "subscriptions",
  "headers": { "Authorization": "Bearer YOUR_PLATFORM_TOKEN" },
  "parameterSchema": [
    { "name": "subscriptionId", "type": "string", "label": "Enrollment ID", "required": true },
    { "name": "action", "type": "string", "label": "activate or deactivate", "required": true }
  ]
}

Your endpoint activates the enrollment, then keeps Inboxi in sync, so the inbox panel and the AI agent see the new status and the parent gets a confirmation:

javascript
app.post('/inboxi/actions/subscription', async (req, res) => {
  if (req.get('Authorization') !== `Bearer ${process.env.PLATFORM_TOKEN}`) return res.sendStatus(401);

  const { subscriptionId, action } = req.body;
  const enrollment = await Enrollments.setStatus(subscriptionId, action === 'deactivate' ? 'inactive' : 'active');

  res.json({ message: action === 'deactivate' ? 'Subscription deactivated' : 'Subscription activated' });

  // Afterwards: update the record and notify the parent
  await inboxi.pushData({ dataType: 'enrollments', contactIdentifier: enrollment.parentPhone,
                          externalId: subscriptionId, data: { ...enrollment.toJSON(), status: enrollment.status } });
  if (enrollment.status === 'active') {
    await inboxi.trigger('enrollment.activated', enrollment.parentPhone,
                         { student_name: enrollment.studentName, course_name: enrollment.courseName });
  }
});

Let the AI agent answer parents

With enrollments and grades pushed, the AI agent automatically receives them for the parent's number. In the agent's instructions, tell it how to use them, for example: “When a parent asks about a subscription, check the enrollments data and state the course, status and expiry date. Share the course link if asked.” For live data such as a question bank, add a GET lookup action.

Troubleshooting

SymptomCauseFix
EVENT_NOT_ACTIVEEvent off or misspelledActivate it in Settings › Automated Messages; check the exact name.
MISSING_FIELDSA required variable is missingCompare with GET /api/v1/events/messaging.
Message never arrivesLocal number format or no approved templateSend 201016772111, not 01016772111; check the log's error.
Data not visible in the chatcontactIdentifier differs from the chatting numberKey records to the parent's WhatsApp number.
Duplicate recordsexternalId missing or not stableUse stable IDs like student_123.