Integrating non-native messaging channels into enterprise contact centers often presents complex technical challengesβranging from strict webhook verification protocols to handling media attachments and short-lived OAuth tokens.
In this article, we will examine the structure and implementation of a production-ready Node.js middleware connector that bridges Snapchat for Business (Direct Messaging / Business API) with Genesys Cloud Contact Center using the Genesys Open Messaging REST API v2.
ποΈ Architecture & Message Flow
The middleware service sits between Snapchat's Business Messaging infrastructure and Genesys Cloud, translating webhooks and handling authentication transparently for both sides.
+------------------+ +--------------------+ +----------------------+
| | | | | |
| Snapchat User | <=========> | Snapchat Middleware| <=========> | Genesys Cloud Agent |
| (Snapchat App) | Snapchat | (Node.js) | Open Message| (Genesys Cloud) |
| | Business API| | REST v2 | |
+------------------+ +--------------------+ +----------------------+
Inbound Message Flow (Snapchat User β Genesys Agent)
A user sends a direct text, photo Snap, or video Snap to your business on Snapchat.
Snapchat posts an HMAC-SHA256 signed webhook payload to /webhooks/snapchat.
The connector validates the x-snap-signature header against your configured webhook secret.
The payload mapper converts the Snapchat message structure into the standard Genesys Open Messaging v2 schema.
The connector posts the standardized payload to the Genesys Cloud Inbound Direct Messaging API endpoint.
Outbound Message Flow (Genesys Agent β Snapchat User)
An agent replies inside the Genesys Cloud interaction workspace.
Genesys Cloud posts an outbound webhook payload to /webhooks/genesys.
The connector verifies the incoming x-genesys-token secret header.
An auto-refreshing Snapchat client handles OAuth 2.0 token validation.
The payload mapper formats the outgoing message and dispatches it via the Snapchat Business API.
π Key Features
Bi-Directional Rich Messaging: Complete support for standard text messages, photo Snaps, and video Snaps.
Auto-Refreshing Snapchat OAuth 2.0: Built-in Axios interceptor dynamically checks token validity and handles token refreshes automatically before API calls.
Dual HMAC & Token Security:
Validates inbound Snapchat webhooks using HMAC-SHA256 signature verification (x-snap-signature).
Validates outbound Genesys webhooks via a configurable secret token header (x-genesys-token).
Open Messaging REST API v2 Compliant: Fully aligned with the latest Genesys Cloud Open Messaging specification.
Container-Ready: Includes a multi-stage Dockerfile built on an Alpine base image with automated health check probes (/health).
π Repository Structure
genesys-snapchat-connector/
βββ config/
β βββ default.js # Environment variable configurations & defaults
βββ src/
β βββ services/
β β βββ genesysService.js # Genesys Cloud Open Messaging API v2 integration
β β βββ snapchatService.js # Snapchat Business API client, HMAC, and OAuth logic
β βββ utils/
β β βββ mapper.js # Inbound & outbound payload mapping matrix
β β βββ logger.js # Structured JSON application logger
β βββ server.js # Express server, webhook endpoints, healthcheck
βββ config-templates/
β βββ genesys-integration-config.json # Genesys Integration schema configuration
βββ .env.example # Environment variable template
βββ Dockerfile # Multi-stage production Docker container configuration
βββ package.json # Project dependencies and scripts
βββ README.md # Documentation
π οΈ Code Implementation
- Snapchat Service & Dynamic OAuth Refresh (src/services/snapchatService.js)
Snapchat access tokens expire periodically. Rather than using external cron jobs or background schedules, an Axios interceptor evaluates expiration dynamically prior to dispatching requests.
const axios = require('axios');
const crypto = require('crypto');
const config = require('config');
const logger = require('../utils/logger');
class SnapchatService {
constructor() {
this.accessToken = null;
this.tokenExpiry = null;
this.client = axios.create({
baseURL: 'https://adsapi.snapchat.com/v1',
headers: { 'Content-Type': 'application/json' }
});
// Axios interceptor for dynamic OAuth 2.0 token refresh
this.client.interceptors.request.use(async (reqConfig) => {
await this.ensureValidToken();
reqConfig.headers.Authorization = `Bearer ${this.accessToken}`;
return reqConfig;
});
}
async ensureValidToken() {
const now = Date.now();
// Refresh 60 seconds before actual expiration timestamp
if (!this.accessToken || !this.tokenExpiry || now >= this.tokenExpiry - 60000) {
await this.refreshAccessToken();
}
}
async refreshAccessToken() {
try {
logger.info('Refreshing Snapchat OAuth 2.0 access token...');
const response = await axios.post('https://accounts.snapchat.com/login/oauth2/access_token', null, {
params: {
grant_type: 'refresh_token',
client_id: config.get('snapchat.clientId'),
client_secret: config.get('snapchat.clientSecret'),
refresh_token: config.get('snapchat.refreshToken')
}
});
this.accessToken = response.data.access_token;
this.tokenExpiry = Date.now() + response.data.expires_in * 1000;
logger.info('Snapchat access token refreshed successfully.');
} catch (error) {
logger.error('Failed to refresh Snapchat access token:', error.response?.data || error.message);
throw error;
}
}
verifyHmacSignature(rawBody, signatureHeader) {
const secret = config.get('snapchat.webhookSecret');
if (!secret || !signatureHeader) return false;
const computedHmac = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(`sha256=${computedHmac}`),
Buffer.from(signatureHeader)
);
}
async sendMessage(recipientId, messagePayload) {
return this.client.post(/organizations/${config.get('snapchat.organizationId')}/messages, {
recipient_id: recipientId,
body: messagePayload
});
}
}
module.exports = new SnapchatService();
- Payload Transformation Matrix (src/utils/mapper.js)
The mapper module handles translating incoming Snapchat structures to Genesys Open Messaging REST API v2 schemas, as well as converting outbound Genesys events to Snapchat Business API parameters.
const config = require('config');
/**
- Maps incoming Snapchat message payload to Genesys Open Messaging REST v2 schema. */ function snapchatToGenesys(snapchatEvent) { const { sender_id, message_id, timestamp, content_type, text, media_url } = snapchatEvent;
const genesysPayload = {
id: message_id,
channel: {
id: config.get('genesys.integrationId'),
platform: 'Open',
type: 'Private',
messageId: message_id,
to: { id: config.get('snapchat.organizationId') },
from: {
id: sender_id,
idType: 'Opaque',
nickname: SnapUser_${sender_id.substring(0, 6)}
},
time: new Date(timestamp).toISOString()
},
type: 'Text',
text: text || ''
};
// Attach media if image or video Snap
if (media_url && (content_type === 'IMAGE' || content_type === 'VIDEO')) {
genesysPayload.type = 'Structured';
genesysPayload.text = text || (content_type === 'IMAGE' ? 'Sent an Image Snap' : 'Sent a Video Snap');
genesysPayload.media = [
{
url: media_url,
mediaType: content_type === 'IMAGE' ? 'image/jpeg' : 'video/mp4'
}
];
}
return genesysPayload;
}
/**
- Maps outbound Genesys message payload to Snapchat Business API payload. */ function genesysToSnapchat(genesysEvent) { const recipientId = genesysEvent.channel?.to?.id; const textContent = genesysEvent.text || ''; let mediaUrl = null;
if (genesysEvent.media && genesysEvent.media.length > 0) {
mediaUrl = genesysEvent.media[0].url;
}
return {
recipientId,
messagePayload: {
text: textContent,
...(mediaUrl && { media_url: mediaUrl })
}
};
}
module.exports = {
snapchatToGenesys,
genesysToSnapchat
};
- Server & Webhook Endpoints (src/server.js)
const express = require('express');
const config = require('config');
const snapchatService = require('./src/services/snapchatService');
const genesysService = require('./src/services/genesysService');
const mapper = require('./src/utils/mapper');
const logger = require('./src/utils/logger');
const app = express();
// Middleware to capture raw body for HMAC verification
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf;
}
}));
// Healthcheck endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
});
// Snapchat Webhook (Inbound)
app.post('/webhooks/snapchat', async (req, res) => {
const signature = req.headers['x-snap-signature'];
if (!snapchatService.verifyHmacSignature(req.rawBody, signature)) {
logger.warn('Unauthorized Snapchat webhook signature verification failed.');
return res.status(401).send('Invalid Signature');
}
try {
const snapEvent = req.body;
logger.info(Received inbound message from Snapchat user: ${snapEvent.sender_id});
const genesysPayload = mapper.snapchatToGenesys(snapEvent);
await genesysService.sendToGenesys(genesysPayload);
return res.status(200).send('EVENT_RECEIVED');
} catch (error) {
logger.error('Error processing Snapchat webhook:', error);
return res.status(500).send('Internal Server Error');
}
});
// Genesys Cloud Outbound Webhook
app.post('/webhooks/genesys', async (req, res) => {
const token = req.headers['x-genesys-token'];
const expectedToken = config.get('genesys.outboundSecret');
if (token !== expectedToken) {
logger.warn('Unauthorized Genesys Cloud outbound token verification failed.');
return res.status(401).send('Unauthorized Token');
}
try {
const genesysEvent = req.body;
logger.info(Received outbound message from Genesys Agent for interaction: ${genesysEvent.id});
const { recipientId, messagePayload } = mapper.genesysToSnapchat(genesysEvent);
await snapchatService.sendMessage(recipientId, messagePayload);
return res.status(200).send('MESSAGE_DISPATCHED');
} catch (error) {
logger.error('Error processing Genesys outbound webhook:', error);
return res.status(500).send('Internal Server Error');
}
});
const PORT = config.get('server.port') || 3000;
app.listen(PORT, () => {
logger.info(Snapchat Genesys Open Messaging Connector running on port ${PORT});
});
βοΈ Environment Configuration
Copy .env.example to .env and fill in your developer credentials:
Server Configuration
PORT=3000
NODE_ENV=production
Snapchat Business API Settings
SNAPCHAT_CLIENT_ID=your_snapchat_client_id
SNAPCHAT_CLIENT_SECRET=your_snapchat_client_secret
SNAPCHAT_REFRESH_TOKEN=your_snapchat_refresh_token
SNAPCHAT_ORGANIZATION_ID=your_snapchat_org_id
SNAPCHAT_WEBHOOK_SECRET=your_snapchat_webhook_hmac_secret
Genesys Cloud Settings
GENESYS_ENVIRONMENT=mypurecloud.com
GENESYS_INTEGRATION_ID=your_genesys_open_messaging_integration_id
GENESYS_OUTBOUND_SECRET=your_genesys_outbound_webhook_token
π₯οΈ Genesys Cloud Open Messaging Setup
In Genesys Cloud Admin, navigate to Admin > Message > Open Messaging.
Click Create New Integration:
Outbound Notification Webhook URL: https:///webhooks/genesys
Secret Token Header: x-genesys-token
Secret Token: Must match GENESYS_OUTBOUND_SECRET in .env.
Copy the system-generated Integration ID and set it as GENESYS_INTEGRATION_ID in your runtime environment.
π³ Container Deployment
This repository includes a multi-stage Docker build targeting Node 20 Alpine for optimized production size and security compliance:
Build Stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
Production Stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
USER node
CMD ["node", "src/server.js"]
Quick Commands
1. Local Development
npm install
npm run dev
2. Docker Deployment
docker build -t genesys-snapchat-connector .
docker run -d -p 3000:3000 --env-file .env --name snapchat-connector genesys-snapchat-connector
π Endpoint Summary
Method
Endpoint
Description
GET
/health
Health check endpoint for Docker / Kubernetes probes
POST
/webhooks/snapchat
Webhook target for inbound Snapchat direct messages
POST
/webhooks/genesys
Webhook target for outbound agent messages from Genesys
Conclusion
This middleware pattern allows enterprise contact centers to integrate custom social channels like Snapchat into Genesys Cloud while maintaining standard security and protocol requirements. By encapsulating authentication management, HMAC verification, and data mapping inside dedicated service modules, the underlying messaging pipeline remains reliable and simple to extend.
Top comments (0)