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
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.
| dataType | externalId | Typical data |
|---|---|---|
parent | parent_4521 | parent_name, email, relation |
student | student_9871 | student_name, grade, school, status |
enrollments | enrollment_3341 | course_name, status, start_date, expiry_date |
grades | grade_exam101_9871 | exam_name, course_name, score, max_score |
attendance | att_2026-10-10_9871 | date, 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 platform | Push (external data) | Then trigger (event) |
|---|---|---|
| New student registers | parent + student | custom.registered |
| Student enrolls in a course | enrollments | enrollment.created |
| Payment activates a subscription | enrollments (status: active) | enrollment.activated |
| Subscription expires | enrollments (status: expired) | enrollment.expired |
| Exam graded | grades | custom.exam_result |
| Daily attendance | attendance (bulk) | - |
| Monthly report | grades + attendance | student.performance_report |
Events starting with custom. are ones you create with New Custom Event; the rest are built in.
A small client
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
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:
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:
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
| Symptom | Cause | Fix |
|---|---|---|
EVENT_NOT_ACTIVE | Event off or misspelled | Activate it in Settings › Automated Messages; check the exact name. |
MISSING_FIELDS | A required variable is missing | Compare with GET /api/v1/events/messaging. |
| Message never arrives | Local number format or no approved template | Send 201016772111, not 01016772111; check the log's error. |
| Data not visible in the chat | contactIdentifier differs from the chatting number | Key records to the parent's WhatsApp number. |
| Duplicate records | externalId missing or not stable | Use stable IDs like student_123. |