Webhooks
Webhooks allow your application to receive real-time notifications when events occur in your EZ Texting account. Instead of polling the API for updates, you register a URL that receives HTTP POST requests when events happen.
How Webhooks Work
- Register a webhook URL via the API or your EZ Texting dashboard
- When an event occurs (e.g., message delivered, reply received), EZ Texting sends an HTTP POST to your URL
- Your server processes the payload and responds with a
200 OK
Managing Webhooks via the API
Use the Webhooks endpoints to create, list, and delete webhooks programmatically.
Webhook Event Types
| Type | Description |
|---|---|
inbound_text.received | Fired when an incoming SMS/MMS is received from a contact |
keyword.opt_in | Fired when a contact opts in via a keyword |
contact.created | Fired when a new contact is created in your account |
Payload Examples
When an event fires, EZ Texting POSTs a JSON body to your callback URL. Payload shape varies by event type — your handler can route on the type field.
inbound_text.received
A contact replied to one of your numbers or sent an incoming message.
{
"type": "inbound_text.received",
"eventId": "evt_8f2b1c4a",
"receivedAt": "2026-04-16T14:32:11Z",
"fromNumber": "15551234567",
"toNumber": "15559876543",
"message": "Yes, please confirm my appointment",
"messageType": "SMS",
"contact": {
"phoneNumber": "15551234567",
"firstName": "Jane",
"lastName": "Doe"
}
}keyword.opt_in
A contact texted one of your registered keywords to opt in.
{
"type": "keyword.opt_in",
"eventId": "evt_4d9a2e71",
"occurredAt": "2026-04-16T14:35:02Z",
"keyword": "JOIN",
"phoneNumber": "15551234567",
"groupIds": ["987654"],
"contact": {
"phoneNumber": "15551234567",
"optOut": false
}
}contact.created
A new contact was created — via API, upload, web widget, or keyword opt-in.
{
"type": "contact.created",
"eventId": "evt_b71e30f5",
"createdAt": "2026-04-16T14:38:45Z",
"source": "API",
"contact": {
"phoneNumber": "15551234567",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"optOut": false
}
}Create a Webhook
POST https://a.eztexting.com/v1/webhooks/subscriptions
Content-Type: application/json
{
"type": "inbound_text.received",
"callbackUrl": "https://your-app.com/webhooks/eztexting"
}List Webhooks
GET https://a.eztexting.com/v1/webhooks/subscriptionsDelete a Webhook
DELETE https://a.eztexting.com/v1/webhooks/subscriptions/{id}Verifying Webhook Signatures
When you create a webhook with a secret, EZ Texting signs every payload with an HMAC-SHA256 hash sent in the request headers. Always verify this signature to ensure the request actually came from EZ Texting and wasn't spoofed.
How it works
- When creating the webhook, include a
secretin the request body - EZ Texting computes
HMAC-SHA256(secret, raw_body)for each webhook delivery - The signature is sent in the
X-EzTexting-Signatureheader - Your server recomputes the HMAC and compares it — reject the request if they don't match
const crypto = require('crypto');
const SECRET = process.env.EZTEXTING_WEBHOOK_SECRET;
app.post('/webhooks/eztexting',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['x-eztexting-signature'];
const expected = crypto
.createHmac('sha256', SECRET)
.update(req.body)
.digest('hex');
if (!sig || !crypto.timingSafeEqual(
Buffer.from(sig), Buffer.from(expected)
)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
processWebhookEvent(event); // async
res.status(200).send('OK');
}
);Best Practices
- Always verify signatures— Never trust a webhook payload without checking the HMAC. Use constant-time comparison (
timingSafeEqualin Node,hmac.compare_digestin Python,hash_equalsin PHP) to prevent timing attacks. - Respond quickly — Return a
200response as fast as possible. Process webhook data asynchronously if needed. If your server takes too long, EZ Texting may retry or disable the webhook. - Handle duplicates— Webhooks may be sent more than once. Use the
eventIdfield for idempotent processing — store processed IDs and skip duplicates. - Use HTTPS— Always use an HTTPS URL for your webhook endpoint to ensure data is encrypted in transit.
- Store the secret securely— Keep your webhook secret in environment variables, not in source code. Rotate it periodically by creating a new webhook with a new secret and deleting the old one.