DEV Community

Cover image for Building a Production-Ready Snapchat for Business Connector for Genesys Cloud Open Messaging v2
Bhargav Bhatt
Bhargav Bhatt

Posted on Fully Autonomous

Building a Production-Ready Snapchat for Business Connector for Genesys Cloud Open Messaging v2

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

  1. 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;
});
Enter fullscreen mode Exit fullscreen mode

}

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;
}
Enter fullscreen mode Exit fullscreen mode

}

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)
);
Enter fullscreen mode Exit fullscreen mode

}

async sendMessage(recipientId, messagePayload) {
return this.client.post(/organizations/${config.get('snapchat.organizationId')}/messages, {
recipient_id: recipientId,
body: messagePayload
});
}
}

module.exports = new SnapchatService();

  1. 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
};

  1. 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');
Enter fullscreen mode Exit fullscreen mode

} 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');
Enter fullscreen mode Exit fullscreen mode

} 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)