Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add messaging-inbound-webhook example #198

Merged
merged 4 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## v2

### v2.0.1

- Add Messaging Inbound Webhook example

### v2.0.0

Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,16 @@ Telnyx, to the `constructEvent()` function; this will not work with a parsed
(i.e., JSON) request body.

You can find an example of how to use this with [Express](https://expressjs.com/)
in the [`examples/webhook-signing`](examples/webhook-signing) folder, but here's
in the [`examples/messaging-inbound-webhook`](examples/messaging-inbound-webhook) folder, but here's
what it looks like:

```typescript
const event = telnyx.webhooks.constructEvent(
webhookRawBody,
webhookTelnyxSignatureHeader,
webhookRawBody, // JSON stringified
webhookTelnyxSignatureHeader, // Buffer from base 64 encoded signature
webhookTelnyxTimestampHeader,
publicKey,
publicKey, // Buffer from base 64 encoded public key
timeToleranceInSeconds, // Optional, defaults to 300 seconds
);
```

Expand All @@ -179,11 +180,11 @@ You can find an example of how to use this with [Express](https://expressjs.com/
const timeToleranceInSeconds = 300; // Will validate signatures of webhooks up to 5 minutes after Telnyx sent the request
try {
telnyx.webhooks.signature.verifySignature(
webhookRawBody,
webhookTelnyxSignatureHeader,
webhookRawBody, // JSON stringified
webhookTelnyxSignatureHeader, // Buffer from base 64 encoded signature
webhookTelnyxTimestampHeader,
publicKey,
timeToleranceInSeconds,
publicKey, // Buffer from base 64 encoded public key
timeToleranceInSeconds, // Optional, defaults to 300 seconds
);
} catch (e) {
console.log('Failed to validate the signature');
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.0
2.0.1
3 changes: 3 additions & 0 deletions examples/messaging-inbound-webhook/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TELNYX_PUBLIC_KEY=
TELNYX_API_KEY=
TELNYX_APP_PORT=8000
9 changes: 9 additions & 0 deletions examples/messaging-inbound-webhook/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
parserOptions: {
ecmaVersion: 6,
},
rules: {
'new-cap': 'off',
'no-console': 'off',
},
};
1 change: 1 addition & 0 deletions examples/messaging-inbound-webhook/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.17.0
2 changes: 2 additions & 0 deletions examples/messaging-inbound-webhook/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
nodejs 20.17.0
npm 10.8.1
9 changes: 9 additions & 0 deletions examples/messaging-inbound-webhook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Node SDK Example - Messaging Webhook

To test this run `npm install` and then run the main script in `package.json` with `ts-node`:

```bash
npm run start
```

> don't forget to populate your Telnyx API Key with `export TELNYX_API_KEY=KEY...` and Telnyx Public Key with `export TELNYX_PUBLIC_KEY=KEY...`
13 changes: 13 additions & 0 deletions examples/messaging-inbound-webhook/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
if (!process.env.TELNYX_API_KEY) {
console.error('Please set the environmental variable: TELNYX_API_KEY');
process.exit();
}

if (!process.env.TELNYX_PUBLIC_KEY) {
console.error('Please set the environmental variable: TELNYX_PUBLIC_KEY');
process.exit();
}

export const TELNYX_API_KEY = process.env.TELNYX_API_KEY;
export const TELNYX_PUBLIC_KEY = process.env.TELNYX_PUBLIC_KEY;
export const TELNYX_APP_PORT = process.env.TELNYX_APP_PORT || 8000;
33 changes: 33 additions & 0 deletions examples/messaging-inbound-webhook/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import express from 'express';
import * as config from './config';
import Telnyx from 'telnyx';

import messaging from './messaging';

const app = express();

const telnyx = new Telnyx(config.TELNYX_API_KEY);

app.use(express.json());

app.use(function webhookValidator(req, res, next) {
try {
telnyx.webhooks.constructEvent(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned this in Slack, but I remember reading this about buffer: https://sindresorhus.com/blog/goodbye-nodejs-buffer
It's pretty esoteric, but essentially:

Telnyx is internally expecting bytes for the signature, public key, etc. In Node.js, Buffer is the idiomatic way to handle those bytes. Under the hood, Buffer is a subclass of Uint8Array, so very likely converting or comparing those values as bytes anyway. In other words, Buffer works—and is what nearly everyone uses in Node.

But from a pure JavaScript/TypeScript perspective, telnyx.webhooks.constructEvent() should also work if you pass in a Uint8Array instead of a Buffer, as long as it actually accepts typed arrays (most Node libraries do). I tested using the following:

const signatureEd25519 = Uint8Array.from(
  atob(req.header('telnyx-signature-ed25519')), 
  c => c.charCodeAt(0)
);
const publicKey = Uint8Array.from(
  atob(config.TELNYX_PUBLIC_KEY), 
  c => c.charCodeAt(0)
);

telnyx.webhooks.constructEvent(
  JSON.stringify(req.body, null, 2),
  signatureEd25519,
  req.header('telnyx-timestamp'),
  publicKey,
  300
);

Don't think it matters, there’s no functional difference in Node, but could move closer to fully “browser-friendly” code if that is one of the goals.

JSON.stringify(req.body, null, 2),
Buffer.from(req.header('telnyx-signature-ed25519')!, 'base64'),
req.header('telnyx-timestamp')!,
Buffer.from(config.TELNYX_PUBLIC_KEY, 'base64'),
300,
);
next();
} catch (e) {
const message = (e as Error).message;
console.log(`Invalid webhook message: ${message}`);
res.status(400).send(`Webhook error: ${message}`);
}
});

app.use('/messaging', messaging);

app.listen(config.TELNYX_APP_PORT);
console.log(`Server listening on port ${config.TELNYX_APP_PORT}`);
29 changes: 29 additions & 0 deletions examples/messaging-inbound-webhook/messaging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import express from 'express';
import Telnyx from 'telnyx';

const router = express.Router();

router
.route('/inbound')
.post(async function inboundMessageController(req, res) {
res.sendStatus(200); // Play nice and respond to webhook
const event = (req.body as Telnyx.events.InboundMessageEvent).data;

if (event?.payload) {
console.log(`Received inbound message with ID: ${event?.payload?.id}`);
const toNumber = event.payload.to?.at(0)?.phone_number as string;
const fromNumber = event.payload.from?.phone_number as string;
const type = event.payload.type;
const text = event.payload.text;

console.log('Type: ', type);
console.log('Text: ', text);
console.log('From: ', fromNumber);
console.log('To: ', toNumber);
} else {
console.error('Failed to receive webhook payload');
console.error(JSON.stringify(event?.payload, null, 2));
}
});

export default router;
Loading
Loading