Send Media With UnifyPort: URL and Delivery Troubleshooting
To send an image, video, audio file, or document through UnifyPort, use POST /v1/messages with a supported message.type and a non-empty source in message.url, message.file_url, or message.file_key. URL sources must be absolute HTTP(S) URLs. A local filename is not a reachable media URL, and a response with status: accepted is not a delivery receipt.
Key takeaways
- Check the selected messaging account’s channel before enabling a media type.
- Start with one explicit, reachable URL; do not guess precedence between source fields.
- Outbound
messagefields differ from inbounddata.message.attachments[]. - Diagnose source access, request validation, account readiness, and delivery separately.
Choose the sending contract first
UnifyPort’s unofficial interface uses the media-send contract. It is not Telegram’s Bot API or LINE’s Messaging API. For example, the Telegram file-sending reference documents file identifiers, HTTP URLs, and multipart uploads for its own methods. Those mechanisms do not establish that UnifyPort accepts a Telegram file_id as file_key or supports the same upload request.
| Input | What the UnifyPort reference establishes |
|---|---|
message.type | Media types include image, video, audio, document, and file; channel support still applies |
message.url or message.file_url | A non-empty absolute HTTP(S) source URL |
message.file_key | A documented source alternative, not a reason to invent a key or upload endpoint |
message.caption | Optional for image, video, document, and file |
provider_data.seconds | Optional non-negative integer duration for WhatsApp audio and video |
For WhatsApp video, the documented duration range is 0–4294967295; provider_data.waveform remains audio-only. Do not populate seconds by copying an inbound duration_ms value unchanged. If you do not need duration metadata, leave it out rather than guessing.
The current message-support matrix does not map TikTok audio or document/file sends, marks X audio support as partial, and treats whatsapp-protocol separately from whatsapp. A shared endpoint is not a promise of identical media capabilities or limits.
Prepare the file before constructing the request
Use this sequence as application guidance, not as an additional platform guarantee:
- Confirm the operator may send through the selected account to the selected conversation. Use the conversation ID and type, not the author’s ID from a group message.
- Check authentication and
runtime_status; authorization alone does not prove the account is connected. - Put the intended bytes at a controlled, reachable source. A browser page that requires your login is not proof that the sender can retrieve the file.
- Test retrieval without browser cookies from a separate server context. Inspect whether the response contains the expected media rather than an HTML login or error page.
- If your storage uses expiring URLs, arrange validity for the expected queue and retrieval period. The send reference does not promise a universal fetch deadline; a preflight success cannot guarantee later access.
Prefer HTTPS, narrow access, and an application-approved storage origin. Keep signed query strings and credentials out of logs. These are security recommendations, not claims about undocumented UnifyPort network filtering.
Construct a URL-based media request
This JavaScript helper builds a request body from your application’s selected account, conversation, and approved media URL. It neither uploads files nor proves remote reachability. Check provider support and access authorization before calling it.
function buildMediaRequest({ accountId, conversation, type, url, caption }) {
const types = new Set(['image', 'video', 'audio', 'document', 'file']);
if (!accountId || !conversation?.id || !conversation?.type) {
throw new Error('Account and conversation are required');
}
if (!types.has(type)) throw new Error('Unsupported media type');
const source = new URL(url);
if (!['http:', 'https:'].includes(source.protocol) ||
source.username || source.password) {
throw new Error('Use an approved HTTP(S) media source');
}
const message = { type, url: source.href };
if (caption !== undefined) {
if (type === 'audio' || typeof caption !== 'string') {
throw new Error('Caption is not valid for this request');
}
message.caption = caption;
}
return {
account_id: accountId,
to: { id: conversation.id, type: conversation.type },
message,
};
}
Submit the resulting JSON to POST /v1/messages using server-side X-Api-Key authentication and Content-Type: application/json. The helper intentionally supplies just message.url. The documentation requires at least one source but does not establish a precedence rule for conflicting sources.
Do not paste an inbound attachment object into the send body. The inbound media mapping guide describes attachments[].type, url, mimetype, and related metadata; those are receiving fields, not a complete outbound request. If the original locator has expired, use the download-recovery guide to identify the correct recovery contract before attempting a relay.
Diagnose the failing boundary
| Observation | Next check | Avoid |
|---|---|---|
Local path, relative URL, or data: URL supplied | Prepare an absolute HTTP(S) media source | Renaming a path to file_key |
| URL works only in your browser | Authentication dependency, expiry, redirects, and actual returned bytes | Assuming your browser session transfers to the sender |
unsupported_message_type | Selected provider and media type | Repeating the unchanged request |
provider_not_ready | Authentication and runtime state | Reauthorizing an already authorized account without diagnosis |
| Request times out | Preserve the outbound operation and investigate its uncertain result | Automatically resending and potentially duplicating the message |
Response says accepted | Record returned identifiers; inspect supported delivery evidence separately | Showing “delivered” or “read” immediately |
The error reference defines machine-readable errors; branch on those rather than guessing from a human-readable message. Save request_id for diagnostics, not as a deduplication token. The request-tracing guide explains that boundary.
If consuming receipts, use the event reference and provider event matrix. Verify webhook signatures and tolerate duplicate or out-of-order events. Not every provider emits every receipt, so absent confirmation should remain unknown rather than trigger another send.
Acceptance checks and FAQ
Before production, propose tests for a reachable file, an expired source, a login-page response, an unsupported channel/type pair, a disconnected account, and a lost send response. No live test results are claimed here.
Can I upload a local file directly with this JSON request?
The documented media request uses URL or file-key sources. This article does not establish a multipart upload endpoint. Host the file through an authorized storage workflow and use a reachable URL.
Can I use Telegram file_id as file_key?
No such equivalence is documented. Keep Telegram’s native file identifiers separate from UnifyPort source fields.
Does accepted mean the recipient received the file?
No. It records acceptance, not a delivery or read receipt. Keep those states separate.
Next step and sources
Follow the Send images and files guide with one authorized test conversation and one approved media source before enabling queued or automated sends.
References checked on 2026-10-09:
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.