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

  1. Register a webhook URL via the API or your EZ Texting dashboard
  2. When an event occurs (e.g., message delivered, reply received), EZ Texting sends an HTTP POST to your URL
  3. 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

TypeDescription
inbound_text.receivedFired when an incoming SMS/MMS is received from a contact
keyword.opt_inFired when a contact opts in via a keyword
contact.createdFired 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/subscriptions

Delete 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

  1. When creating the webhook, include a secret in the request body
  2. EZ Texting computes HMAC-SHA256(secret, raw_body) for each webhook delivery
  3. The signature is sent in the X-EzTexting-Signature header
  4. 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 (timingSafeEqual in Node,hmac.compare_digest in Python, hash_equals in PHP) to prevent timing attacks.
  • Respond quickly — Return a 200 response 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 theeventId field 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.
Tip: For testing webhooks locally during development, use tools like ngrokto expose your local server to the internet. You can inspect payloads in ngrok's dashboard to verify signatures are arriving correctly.