POSTs a JSON body to that URL every time something significant happens. Your server inspects the event field, does its thing, and returns 200 OK within 30 seconds.š New here? If you are integrating for the first time, start at the Quickstart. If your signature verification is failing, jump straight to š Signature Verification ā that section has been updated to correct a previous documentation error.
event field to route them.| Event | Fires when | Applies to | Dedicated docs page |
|---|---|---|---|
domain.status_changed | A domain's lifecycle status transitions (purchased, activated, DNS propagating, cancelled, etc.) | All domains, regardless of provider | Event: Domain Status Changed |
mailbox.status_changed | A mailbox's lifecycle status transitions (provisioning, activation, suspension, cancellation, etc.) | Google Workspace and Microsoft 365 mailboxes | Event: Mailbox Status Changed |
consent_request.status_changed | An OAuth consent request changes state (customer opens consent link, grants, denies, times out) | Google Workspace & Microsoft 365 onboarding flows | Events: OAuth Consent & Client ID Changes |
client_id_request.status_changed | A Google OAuth Client ID add/update request changes state in the customer's Workspace Admin Console | Google only (domain-wide delegation) | Events: OAuth Consent & Client ID Changes |
https://your-app.com/inboxkit/webhook). We strongly recommend an HTTPS endpoint ā mailbox webhooks include plaintext credentials and HTTPS is what actually protects them in transit.application/json, parse the body, switch on event, respond with 200 OK within 30 seconds.X-InboxKit-Signature header on every request before trusting the payload. See š Signature Verification below.team.webhook_url) ā the one on Settings ā API ā Webhooks. This is the normal place to set it.| Header | Description |
|---|---|
Content-Type | Always application/json. |
User-Agent | Always InboxKit-Webhook/1.0. |
X-InboxKit-Event | Event type string. Use this to route without parsing the body. |
X-InboxKit-Timestamp | ISO 8601 UTC timestamp of when InboxKit generated the delivery. |
X-InboxKit-Signature | sha256=<hex> ā InboxKit sender-identity fingerprint. See Signature Verification section. Only sent if your team has an API key configured. |
{
"event": "<event name>",
"timestamp": "<ISO 8601 UTC>",
"team_id": "<your team uid>",
"team_name": "<your team name>",
"data": { "...event-specific fields..." }
}data object is different for each event ā see the dedicated event pages in this section for the full field list.200 OK is enough.| Your response | InboxKit behavior |
|---|---|
| 2xx (200ā299) | ā Success ā delivery is marked complete, no retry. |
| 3xx (300ā399) | Followed (up to 5 redirects), then evaluated on the final status. |
| 400, 401, 403, 404, 405, 410, 451 | ā Permanent ā no retry. These indicate your endpoint is fundamentally rejecting us (bad payload, auth wall, endpoint doesn't exist, method not allowed, gone, legally blocked). |
| Other 4xx (e.g. 408, 409, 429) | š Retried up to 3 times. If you need InboxKit to slow down, return 429 Too Many Requests ā it is not in the permanent set, so InboxKit will back off and retry. |
| 5xx (500ā599) | š Retried up to 3 times with exponential backoff. |
| Timeout > 30s | š Retried up to 3 times. |
DNS lookup failed (ENOTFOUND), connection refused (ECONNREFUSED), invalid URL | ā Permanent ā no retry. These mean the URL itself is broken; retrying won't help. |
Other network errors (ETIMEDOUT, ECONNRESET, EPIPE, EHOSTUNREACH, etc.) | š Retried up to 3 times. |
data.<entity>.uid ā i.e. data.domain.uid, data.mailbox.uid, data.consent_request.uid, or data.client_id_request.uid. Combine it with event and the new status if you need to de-duplicate on a per-transition basis. InboxKit does not send a separate event_id header.min(2^n seconds, 60 seconds) before retrying.ENOTFOUND, ECONNREFUSED, ERR_INVALID_URL). Permanent failures are marked as failed immediately, with no further attempts.ā ļø IMPORTANT ā PLEASE READ EVEN IF YOU READ THE OLD DOCS. Older versions of this page described X-InboxKit-Signatureas an "HMAC signature". That wording was misleading. It is not an HMAC. The signature is a plain SHA-256 hash of your team API key ā the request body is not part of the hash. If you built verification usingcrypto.createHmac, it will never match. You needcrypto.createHash('sha256'), as shown in the complete Node.js example below.
X-InboxKit-Signature header value is:sha256=<lowercase hex SHA-256 of your team API key>SHA-256(api_key).sha256=.expected = "sha256=" + lowercase_hex(sha256(api_key))X-InboxKit-Signature header and compare it to expected using a constant-time comparison. Reject with 401 if it does not match.X-InboxKit-Signature header of a real webhook ā they should match byte-for-byte.crypto.createHmac instead of crypto.createHash. This is the #1 mistake and it comes from the previous docs wording. There is no HMAC. Use createHash('sha256')."my-key\n" hashes to a completely different value than "my-key". Trim whitespace.===. This is a timing-attack surface. Use crypto.timingSafeEqual (Node), hmac.compare_digest (Python), hash_equals (PHP), subtle.ConstantTimeCompare (Go).sha256= prefix. The full header value includes the sha256= prefix. Don't strip it before comparing, or include it in both sides consistently.EXPECTED_SIG will stop matching as soon as the first new-hash webhook arrives. You will see 401s in your logs until step 3 is done.INBOXKIT_API_KEY environment variable and restart (or SIGHUP / reload) so your app recomputes EXPECTED_SIG.https://webhook.site, copy the unique URL, paste it into InboxKit's webhook settings, and you will see every request in real time in your browser. Great for inspecting the exact payload InboxKit sends.ngrok http 3000 and paste the https://abc123.ngrok.io/inboxkit/webhook URL into InboxKit. Your local dev server will receive real webhooks.createHmac instead of createHash because the old docs said "HMAC".data.<entity>.uid as an idempotency key. Storing (event, entity_uid, new_status) tuples in a short-TTL cache is usually enough.curl --location --request POST 'https://api.inboxkit.com/webhook' \
--header 'X-InboxKit-Signature: sha256=b0344c61d8db38e0f469e85fc6e8c055e0742e8d9a9c5a9e8e4d3f7c5a8b9c0d' \
--header 'X-InboxKit-Event: domain.status_changed' \
--header 'X-InboxKit-Timestamp: 2024-01-10T15:30:45.123Z' \
--header 'User-Agent: InboxKit-Webhook/1.0' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data-raw '{
"event": "domain.status_changed",
"timestamp": "2024-01-10T15:30:45.123Z",
"team_id": "ABC-123-DEF-TEAM",
"team_name": "Marketing Team",
"data": {
"domain": {
"uid": "ABC-123-DEF-DOMAIN",
"name": "yourdomain.com",
"tld": "com",
"status": "active",
"previous_status": "pending_payment",
"dns_propagation_status": "propagated",
"nameservers": [
"ns1.inboxkit.com",
"ns2.inboxkit.com"
],
"renewal_date": "2025-01-10T00:00:00.000Z",
"renewal_status": "na",
"registration_years": 1,
"registration_date": "2024-01-10T00:00:00.000Z",
"price": 12.99,
"connection_type": "purchased",
"forwarding_url": "",
"forwarding_email": "",
"forwarding_status": "",
"enable_mask_forwarding": false,
"dmarc_email": "",
"catch_all_email": "",
"assigned_mailboxes": 0,
"available_mailboxes": 50,
"tags": [
"production"
]
},
"metadata": {
"updated_at": "2024-01-10T15:30:45.123Z",
"workspace_id": "ABC-123-DEF-WORKSPACE"
}
}
}'{
"received": true,
"message": "Webhook processed successfully"
}