← All posts
Tutorial

Webhook Media Albums: Group Photos Without Losing Messages

To assemble a media album from webhook messages, store each child message independently and use the album identifier only to build a grouped view. Never deduplicate children by album ID: that would discard different photos belonging to the same album. When the payload does not provide an expected total, a quiet period can trigger processing, but it cannot prove that every item arrived.

Key takeaways

  • Message identity and album identity solve different problems.
  • Scope both identities to the receiving account and conversation.
  • Persist children before acknowledging delivery; assemble the view afterward.
  • Keep late arrivals recoverable rather than deleting the group when a timer fires.

An album identifier is not a message identifier

Telegram’s official Bot API reference defines Message.media_group_id as an optional identifier of the media group inside that chat. It describes a relationship between messages, not a replacement for each message’s own identity.

UnifyPort uses a different contract. Its standard webhook reference documents optional data.message.album with id, index, and optional total. An album child retains its own data.message.id. The documented album example is WhatsApp; do not infer that every provider always supplies album metadata or that Telegram’s raw field appears in the normalized payload.

If you are implementing attachment extraction first, use the media attachment mapping guide. Grouping is an additional projection, not a different file-download protocol.

IdentityRecommended scopePurpose
Ordinary event IDYour workspace and event streamSuppress repeated delivery of the same event
Child message IDWorkspace, provider, account, conversationStore or update one message
Album IDWorkspace, provider, account, conversationAssociate multiple children
Attachment URLAttached to its child recordLocate media; never use as the grouping key

For a hypothetical three-photo submission, receiving child B twice and child C once means two distinct children are stored—not three. Counting HTTP requests or event arrivals does not measure album completeness.

Build child records before the grouped view

The following JavaScript extracts application storage keys from a validated UnifyPort event. The return names are local application properties, not new API fields. It neither verifies the webhook nor implements persistence.

function albumKeys(workspaceKey, event) {
  if (event.type !== 'message.received') return null;
  const conversation = event.data?.conversation;
  const message = event.data?.message;
  if (!event.provider || !event.account_id ||
      !conversation?.id || !message?.id) {
    throw new Error('Missing message identity');
  }

  const scope = [workspaceKey, event.provider,
    event.account_id, conversation.id];
  return {
    childKey: JSON.stringify([...scope, message.id]),
    albumKey: message.album?.id
      ? JSON.stringify([...scope, message.album.id])
      : null
  };
}

Use the function inside this recommended transaction flow:

  1. Authenticate the raw delivery according to the webhook delivery contract. With signing_secret enabled, verify HMAC-SHA256 over the timestamp, a dot, and the raw body; also check timestamp freshness.
  2. Persist the event and upsert the child using a uniqueness constraint on the scoped message identity. Preserve captions, attachments, sent_at, and available album metadata.
  3. Associate the child with the scoped album. If there is no album ID, retain it as a standalone message; do not guess membership from matching captions or adjacent arrival times.
  4. Commit the intake and durable work item before returning 2xx. Let a worker update the album view and fetch media separately.

Use the same transaction or a durable outbox so a process crash cannot leave a stored child without its aggregation job. Retain each child’s caption rather than letting the last delivery overwrite a single album-wide caption.

Decide when to process without claiming completeness

The following states are suggested application states, not provider events or API fields.

ObservationApplication decision
Consistent total is present and that many distinct children are storedMark the expected count as reached; track media readiness separately
Total is absentProcess a provisional snapshot after a locally chosen quiet period
Totals conflict or indices collidePreserve the children and flag the metadata for investigation
A new child arrives after processingUpdate the projection and apply an explicit late-item policy
A child is redeliveredUpdate idempotently; do not increment the distinct-child count

Serialize projection updates per scoped album, or use transactional version checks. Otherwise concurrent workers can both read the old count and schedule duplicate downstream work. Persist the processing decision with the exact child identities included. If another child arrives later, choose between refreshing the display, issuing a supplemental analysis, or requesting human review. Do not silently rerun a customer-facing send.

Preserve the supplied album.index for presentation, but do not derive an undocumented indexing convention from one sample. An absent or conflicting index should not cause a child to disappear. A timer is your latency policy, not a platform completion signal.

Keep media readiness separate

An album can have all expected message records while a file is still unavailable. UnifyPort documents temporary attachment URLs and oversized-file cases where the URL is omitted. Schedule downloads promptly and show separate attachment success or failure states.

The expired-download recovery guide explains why a Telegram Bot API file-refresh operation cannot simply be applied to a UnifyPort attachment URL. Do not wait indefinitely for the album before preserving available files.

UnifyPort is an unofficial interface, and its normalized stream does not promise identical metadata across channels. It provides no REST message-history read API or guaranteed replay of missed payloads. Keep the official Bot API when you need its native bot contract; do not combine the two schemas in one parser.

Acceptance checks and FAQ

Test duplicate children, reordered arrivals, identical album IDs in different conversations, missing totals, conflicting metadata, and a late child after processing. These are proposed tests, not reported results.

Can album.id be the deduplication key?

Not for child messages. Several distinct messages can share it. Use the scoped child message ID for storage.

Does total prove the files are downloaded?

No. It describes expected group size when supplied. File persistence is a separate milestone.

Should an album without total be discarded?

No. Keep the children and process a provisional view under an explicit application policy. Preserve the ability to add late items.

Next step and sources

Implement child-level storage against the standard event reference before enabling album-level automation.

References checked on 2026-09-29:

UnifyPort API

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.