After You Create a UnifyPort API Key: First Webhook Test Checklist
Your first UnifyPort API key should be tested before you connect a messaging account. The safe sequence is: verify the key with GET /v1/workspace, create a signed webhook endpoint, subscribe to message.received or ["*"], store events on arrival, and only then authorize WhatsApp, Telegram, LINE, TikTok, Zalo, or X.
Key takeaways
- The API key authenticates with the
X-Api-Keyheader; do not put it in browser code or commit it to a repository. - A newly created key’s full secret is returned once as
api_key; later list responses expose onlykey_prefix. - Register the webhook before account authorization, because auth progress and inbound messages arrive as webhook events.
- Enable
signing_secretand verifyX-Device-Signaturewith the raw request body before trusting the payload. - Treat
message.receivedas the first production contract, not as a demo-only event.
If you already have a live key and need to replace it, use the separate zero-downtime API key rotation runbook. If you are starting the inbound architecture from scratch, pair this article with the webhook-first integration checklist.
1. Confirm what the key is allowed to do
UnifyPort is currently available to selected customers; the public docs say to contact the team for workspace access and the first API key. Once you have the key, the first request should be a read-only workspace check, not a message send.
export UNIFYPORT_API_KEY="set-this-in-your-secret-manager"
curl https://api.unifyport.ai/v1/workspace \
-H "X-Api-Key: $UNIFYPORT_API_KEY"
A successful response confirms that the key resolves to one workspace. The Introduction docs also note that every /v1 endpoint authenticates with the X-Api-Key request header and that JSON success or error responses carry a top-level request_id for support and reconciliation.
2. Create a named key, then store the secret once
If your workspace lets you create additional keys, use a name that reflects the runtime that will use it. The Create API key reference documents this response shape: the key record is returned under key, and the full secret is returned once under api_key.
curl -X POST https://api.unifyport.ai/v1/api-keys \
-H "X-Api-Key: $UNIFYPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production inbound worker",
"prefix": "dk_live"
}'
Operational rule: copy the returned api_key directly into your secrets manager and never paste it into an issue, chat log, or client-side environment variable. OWASP’s official Secrets Management Cheat Sheet treats API keys as secrets and covers creation, storage, rotation, revocation, and auditing as one lifecycle.
3. Register the webhook before you connect an account
Do this before QR, code, or session authorization. UnifyPort has no guaranteed replay for missed webhook deliveries, so the durable record is your receiver and database.
export WEBHOOK_SIGNING_SECRET="generate-a-long-random-secret"
curl -X POST https://api.unifyport.ai/v1/webhook-endpoints \
-H "X-Api-Key: $UNIFYPORT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://inbox.example.com/unifyport/webhook",
"status": "active",
"subscribed_events": ["message.received"],
"signing_secret": "'"$WEBHOOK_SIGNING_SECRET"'"
}'
The Create webhook endpoint reference allows exact public standard event names or ["*"]. Use ["*"] when you are building a full account state machine; use message.received when the first milestone is inbound customer messages.
4. Verify the webhook signature against the raw body
The delivery docs define four important headers: X-Device-Event-Id, X-Device-Delivery-Id, X-Device-Timestamp, and X-Device-Signature. The signature is a hex-encoded HMAC-SHA256 of:
<X-Device-Timestamp> + "." + <raw request body>
That raw-body detail matters. If your framework parses JSON first and then serializes it again, the bytes can change and signature verification will fail.
import crypto from 'crypto';
import express from 'express';
const app = express();
const signingSecret = process.env.WEBHOOK_SIGNING_SECRET;
app.post('/unifyport/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Device-Timestamp') || '';
const signature = req.get('X-Device-Signature') || '';
const expected = crypto
.createHmac('sha256', signingSecret)
.update(timestamp + '.')
.update(req.body)
.digest('hex');
const valid = signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) return res.status(401).end();
const event = JSON.parse(req.body.toString('utf8'));
if (event.type === 'message.received') {
console.log(event.provider, event.data.conversation.id, event.data.message.text);
}
res.status(200).end();
});
The deeper reference is Webhook delivery & signature verification. It also covers retries, idempotency, stale timestamp checks, and the rule that any 2xx response acknowledges delivery.
5. Store the message.received envelope as your first contract
A normal inbound message arrives with the stable envelope id, type, provider, account_id, occurred_at, and data. The standard event payload reference shows the fields you should design around:
{
"id": "evt_2f9c1a4b7e",
"type": "message.received",
"provider": "whatsapp",
"account_id": "acc_8c21d0",
"occurred_at": "2026-06-08T12:34:56Z",
"data": {
"conversation": { "id": "8613912345678", "type": "user" },
"sender": { "id": "8613912345678", "type": "user", "name": "Jordan Lee" },
"message": {
"id": "wamid.HBgM",
"text": "Hi - is my order shipped yet?",
"direction": "inbound",
"sent_at": "2026-06-08T12:34:55Z"
}
}
}
Store the top-level event id for idempotency, the provider and account_id for routing, data.conversation.id for queue grouping, data.sender.id for identity, and data.message.id for message-level actions. Once this shape is stored, the same receiver can handle WhatsApp today and add LINE, Telegram, Zalo, TikTok, or X later. If you want an AI-assisted version of the same build, see the AI coding agent auto-reply bot tutorial.
Common first-day mistakes
| Mistake | Why it hurts | Safer move |
|---|---|---|
| Creating the account first | Auth events can arrive before your receiver exists | Create the webhook endpoint first |
| Disabling signing | Anyone who knows the URL can post lookalike JSON | Set signing_secret and verify raw-body HMAC |
| Deduplicating only by delivery attempt | Retries can send the same event more than once | Deduplicate by X-Device-Event-Id or event id |
| Logging the API key | Logs spread secrets into systems you do not audit daily | Put the key in a secrets manager and redact it in logs |
Treating message.received as WhatsApp-only | It is the normalized event used across supported providers | Persist the provider and account fields, not provider-specific assumptions |
FAQ
Can I retrieve the full API key later?
No. The API key creation response returns the full secret once under api_key. List and detail responses expose display-safe metadata such as key_prefix, not the full secret.
Should I subscribe to message.received or ["*"]?
Use message.received for a narrow first inbound test. Use ["*"] when your system needs auth, runtime, message, receipt, conversation, or group events in one receiver.
Is a webhook endpoint required before creating an account?
For a reliable first run, yes. Account authorization progress and live inbound messages are delivered as webhook events, and UnifyPort does not promise a complete replay for missed deliveries.
Does this require an official business account on every platform?
No. UnifyPort provides an unofficial interface for WhatsApp, Telegram, LINE, TikTok, Zalo, and X and can work with personal or ordinary messaging accounts where that is the intended integration model.
Next step
Open the Quickstart with your API key in a secret manager, create the webhook endpoint first, and keep the delivery verification docs beside your receiver implementation.
Sources checked on 2026-09-01
Turn messaging integration into a stable product pipeline.
Start by sending through one API, then bring every inbound message back into your business system with standard events.