SendKuy API

Webhooks

We call you when something happens, signed so you can trust it.

Register an endpoint with POST /webhooks and we POST a JSON event to it when something happens. The URL must be https:// — the payload can carry the text of your customers' messages, and plain http would send that in the clear.

The response that creates an endpoint is the only one that carries the signing secret in full. Every later read answers with secret_preview instead.

Events

EventRaised for
message.sentCampaign messages only
message.deliveredAny message
message.openedCampaign messages only
message.clickedCampaign messages only
message.repliedCampaign messages only
message.bouncedCampaign messages only
message.failedAny message
message.receivedAny message
campaign.completedAny message
contact.createdAny message
contact.updatedAny message

The five marked `campaign_only` are only ever raised for messages born from a campaign. The tables behind single sends do not record those states at all, so subscribing to them and sending one-off messages means waiting for events that will never arrive.

Verifying the signature

Each request carries X-SendKuy-Signature, formatted t={timestamp},v1={hmac}.

`hash_hmac('sha256', timestamp . '.' . rawRequestBody, secret)`. Compute it over the raw body, before any JSON parsing — re-encoding changes the bytes and the signature will not match. Compare with a constant-time comparison, and reject timestamps that are too old to stop replays.

Headers

HeaderMeaning
X-SendKuy-EventThe event name, the same value as `events` on the endpoint.
X-SendKuy-Event-IdThe id of this delivery attempt's event. It stays the same across all six attempts, so use it to make your handler idempotent — a retry is the same event, not a second one.

Retries

6 attempts: the first immediately, then after 1m, 5m, 30m, 2h, 12h.

The first call is immediate; the five entries above are the waits before each following attempt. Answer 2xx quickly — the call times out after 10 seconds, and a slow handler is a failed delivery. Repeated failures eventually disable the endpoint, which shows up as `disabled_at`.