EcoWave Docs

API v1

Webhooks

Status callbacks, signatures, and retries

Pass webhook.url and optional webhook.secret on POST /messages. EcoWave POSTs an envelope with id, event, createdAt, and data for message.accepted, message.sent, message.delivered, and message.failed. Inbound replies may also emit message.received.

responseJSON
{
  "id": "4f2a8b10-1c2d-4e5f-9012-abcdef123456",
  "event": "message.delivered",
  "createdAt": "2026-08-08T09:12:19.000Z",
  "data": {
    "id": "7f8d9e1a-4b2c-4d5e-9f01-234567890abc",
    "organizationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "event": "message.delivered",
    "status": "delivered",
    "from": "EcoWave",
    "to": "+251911234567",
    "segments": 1,
    "providerMessageId": null,
    "errorCode": null,
    "errorMessage": null,
    "createdAt": "2026-08-08T09:12:04.000Z",
    "sentAt": "2026-08-08T09:12:10.000Z",
    "deliveredAt": "2026-08-08T09:12:18.000Z",
    "failedAt": null
  }
}

Verify signatures

When a secret is set, headers include X-EcoWave-Timestamp, X-EcoWave-Delivery, X-EcoWave-Event, and X-EcoWave-Signature as sha256=<hex>. Compute HMAC-SHA256 over timestamp + "." + rawBody, strip the sha256= prefix, then compare in constant time. Deduplicate on X-EcoWave-Delivery and return 2xx within 10 seconds.

Node.js
import crypto from "node:crypto";

function verify(rawBody, timestamp, signatureHeader, secret) {
  const provided = signatureHeader.startsWith("sha256=")
    ? signatureHeader.slice("sha256=".length)
    : signatureHeader;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(provided);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Non-2xx responses and timeouts retry with backoff. Exhausted deliveries land in the webhook DLQ and can be redriven via the API or console.