Stickers
Peel an emoji or image onto a message, send a sticker of its own, place one inside text, and reposition it.
A sticker is an image or emoji stuck to a conversation rather than sent as a photo. You control its position, size and orientation.
Stickers are an iMessage feature. See Protocol Selection for what each protocol supports.
Three ways to send a sticker
Section titled “Three ways to send a sticker”| What the recipient sees | Endpoint | |
|---|---|---|
| Peel onto a message | an emoji or image stuck to an existing bubble, positionable | POST /v3/messages/{messageId}/reactions with type: "sticker" |
| Send it as a message | a sticker in the conversation, no bubble, peelable by the recipient onto anything | POST /v3/chats/{chatId}/messages with a media part carrying sticker: true |
| Place it inline in text | sticker images in the line with the words, in place of the characters they cover | POST /v3/chats/{chatId}/messages with inline_stickers on a text part |
Placement applies only to a peel. A sticker sent as its own message isn’t attached to anything, and an inline sticker is positioned by the character range it replaces.
Peel a sticker onto a message
Section titled “Peel a sticker onto a message”Pass type: "sticker" with what to peel — an emoji, or an image you’ve uploaded:
curl -X POST https://api.linqapp.com/api/partner/v3/messages/{messageId}/reactions \ -H "Authorization: Bearer $LINQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "operation": "add", "type": "sticker", "emoji": "🎉" }'await client.messages.addReaction({messageId}, { operation: "add", type: "sticker", emoji: "🎉",});client.messages.add_reaction( {message_id}, operation="add", type="sticker", emoji="🎉",)client.Messages.AddReaction(context.TODO(), {messageId}, linq.MessageAddReactionParams{ Operation: linq.F("add"), Type: linq.F("sticker"), Emoji: linq.F("🎉"),})| Field | Required | Type | Description |
|---|---|---|---|
operation | Yes | add | remove | Whether to add or remove the reaction |
type | Yes | object | — |
custom_emoji | No | string | Custom emoji string. Required when type is "custom". This is a **tapback** — the emoji sits in the tapback bubble on the corner of the message. To peel an emoji onto the message as a draggable sticker instead, use type "sticker" with `emoji`. |
emoji | No | string | A single emoji to peel onto the message as a sticker. Only valid when type is "sticker". Exactly one of `emoji`, `url` or `attachment_id` is required when type is "sticker". Not to be confused with `custom_emoji`, which produces a tapback. |
url | No | string (uri) | Linq attachment URL of the sticker image — the `download_url` returned by `POST /v3/attachments`. Only valid when type is "sticker". The image must already be stored with us. To send a sticker from elsewhere, upload it with `POST /v3/attachments` first and pass `attachment_id`. Exactly one of `emoji`, `url` or `attachment_id` is required when type is "sticker". |
attachment_id | No | string (uuid) | Reference to a sticker image pre-uploaded via `POST /v3/attachments`. Only valid when type is "sticker". Exactly one of `emoji`, `url` or `attachment_id` is required when type is "sticker". |
part_index | No | integer | Optional index of the message part to react to. If not provided, reacts to the entire message (part 0). |
placement | No | object | — |
Choosing what to peel
Section titled “Choosing what to peel”Pick one of emoji, url or attachment_id.
| Field | What it takes | Pre-upload needed |
|---|---|---|
emoji |
a single emoji | No |
attachment_id |
an image from POST /v3/attachments |
Yes |
url |
the download_url from that same upload |
Yes |
attachment_id and url are two ways of naming the same stored image, so use whichever you have on hand.
Animated images work. A GIF peels onto the bubble and plays there, in whatever shape the file already has.
url takes a Linq attachment address only — upload the image first.
curl -X POST https://api.linqapp.com/api/partner/v3/messages/{messageId}/reactions \ -H "Authorization: Bearer $LINQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "operation": "add", "type": "sticker", "attachment_id": "550e8400-e29b-41d4-a716-446655440000" }'await client.messages.addReaction({messageId}, { operation: "add", type: "sticker", attachment_id: "550e8400-e29b-41d4-a716-446655440000",});client.messages.add_reaction( {message_id}, operation="add", type="sticker", attachment_id="550e8400-e29b-41d4-a716-446655440000",)client.Messages.AddReaction(context.TODO(), {messageId}, linq.MessageAddReactionParams{ Operation: linq.F("add"), Type: linq.F("sticker"), AttachmentId: linq.F("550e8400-e29b-41d4-a716-446655440000"),})A message can hold several parts — text, an image, a link. part_index picks which one the sticker lands on, counting from 0. Omit it and the sticker goes on the first. It works the same way for a tapback.
Emoji stickers vs. emoji tapbacks
Section titled “Emoji stickers vs. emoji tapbacks”Both take an emoji and they look nothing alike:
| What you get | |
|---|---|
type: "sticker" + emoji |
the emoji peeled onto the bubble — draggable, resizable, rotatable |
type: "custom" + custom_emoji |
a tapback in the small bubble at the message’s corner |
Reuse one upload
Section titled “Reuse one upload”For images, the attachments API doubles as your sticker library: upload once, then peel the same attachment_id as often as you like. A sticker you send repeatedly — a logo, a mascot, a seasonal set — should be uploaded once and referenced by id, not re-uploaded on every send. (Emoji need none of this; there’s nothing to store.)
One caveat on tiers — a sticker meant for reuse belongs on the persistent tier, which is the default. Attachments on the ephemeral tier are removed within roughly 24–48 hours of upload, taking the sticker with them.
Placement
Section titled “Placement”placement sets where the sticker lands on the target bubble. Every field is optional — omit the object entirely for the native-looking default: centred, unrotated, at the size that source gets natively.
| Field | Required | Type | Description |
|---|---|---|---|
x | No | number (double) | Horizontal position on the target bubble, from -1 (far left) to 1 (far right). 0 is centred. |
y | No | number (double) | Vertical position on the target bubble, from -1 (top) to 1 (bottom). 0 is centred. |
scale | No | number (double) | How large the sticker is drawn. Omit it for the default size — equivalent to `1` for an image, or `0.5` for an emoji. Values outside 0.05–2.5 are clamped rather than rejected. Scale is linear, so 2.5 is a little over six times the area. |
rotation | No | number (double) | Clockwise rotation in degrees. |
The default size depends on what you peeled: an emoji lands smaller than an image, so scale: 1 is not the same size for both. Out-of-range values clamp rather than reject, so a size or angle never fails a send.
Move a sticker after sending
Section titled “Move a sticker after sending”PATCH /v3/messages/{messageId}/reactions/{reactionId}
Repositioning reaches every device in the conversation, exactly as dragging the sticker by hand would. Omitted fields keep their current value.
curl -X PATCH https://api.linqapp.com/api/partner/v3/messages/{messageId}/reactions/{reactionId} \ -H "Authorization: Bearer $LINQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "placement": { "x": 0.6, "y": 0.5, "scale": 0.75 } }'await client.messages.updateStickerPlacement({messageId}, {reactionId}, { placement: { x: 0.6, y: 0.5, scale: 0.75, },});client.messages.update_sticker_placement( {message_id}, {reaction_id}, placement={ "x": 0.6, "y": 0.5, "scale": 0.75, },)client.Messages.UpdateStickerPlacement(context.TODO(), {messageId}, {reactionId}, linq.MessageUpdateStickerPlacementParams{ Placement: linq.F(map[string]any{ X: linq.F(0.6), Y: linq.F(0.5), Scale: linq.F(0.75), }),})reactionId is the id on the reaction as it appears on the message, or reaction_id on the reaction.added webhook. Stickers stack, so the target message alone can’t identify one — this id is the only thing that distinguishes one sticker from another on the same bubble.
Only stickers can be repositioned. A tapback has no placement, so a non-sticker reactionId is rejected.
Standalone stickers
Section titled “Standalone stickers”Set sticker: true on a media part. The image arrives as a sticker with no bubble, and the recipient can peel it onto any message in the conversation.
curl -X POST https://api.linqapp.com/api/partner/v3/chats/{chatId}/messages \ -H "Authorization: Bearer $LINQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": { "parts": [ { "type": "media", "url": "https://cdn.linqapp.com/attachments/example/sticker.png", "sticker": true } ] } }'await client.chats.messages.send({chatId}, { message: { parts: [ { type: "media", url: "https://cdn.linqapp.com/attachments/example/sticker.png", sticker: true, }, ], },});client.chats.messages.send( {chat_id}, message={ "parts": [ { "type": "media", "url": "https://cdn.linqapp.com/attachments/example/sticker.png", "sticker": True, }, ], },)client.Chats.Messages.Send(context.TODO(), {chatId}, linq.ChatMessageSendParams{ Message: linq.F(map[string]any{ Parts: linq.F([]any{ map[string]any{ Type: linq.F("media"), Url: linq.F("https://cdn.linqapp.com/attachments/example/sticker.png"), Sticker: linq.F(true), }, }), }),})- Photos are cut out automatically. The subject is lifted off its background. An image that’s already cut out is sent as-is.
- If no subject is found, the image sends as a photo.
- Animated images send as animated stickers, without a cutout.
- iMessage only. On SMS and RCS the image sends as a photo.
Inline stickers
Section titled “Inline stickers”inline_stickers places sticker images in the line of text. Each entry replaces a character range of the text part’s value, and takes its image from exactly one of url or attachment_id.
curl -X POST https://api.linqapp.com/api/partner/v3/chats/{chatId}/messages \ -H "Authorization: Bearer $LINQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": { "parts": [ { "type": "text", "value": "Happy birthday 🎂! 🎉🎉", "inline_stickers": [ { "range": [ 15, 17 ], "attachment_id": "550e8400-e29b-41d4-a716-446655440000" }, { "range": [ 19, 21 ], "attachment_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" }, { "range": [ 21, 23 ], "attachment_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" } ] } ] } }'await client.chats.messages.send({chatId}, { message: { parts: [ { type: "text", value: "Happy birthday 🎂! 🎉🎉", inline_stickers: [ { range: [15, 17], attachment_id: "550e8400-e29b-41d4-a716-446655440000", }, { range: [19, 21], attachment_id: "7c9e6679-7425-40de-944b-e07fc1f90ae7", }, { range: [21, 23], attachment_id: "7c9e6679-7425-40de-944b-e07fc1f90ae7", }, ], }, ], },});client.chats.messages.send( {chat_id}, message={ "parts": [ { "type": "text", "value": "Happy birthday 🎂! 🎉🎉", "inline_stickers": [ { "range": [15, 17], "attachment_id": "550e8400-e29b-41d4-a716-446655440000", }, { "range": [19, 21], "attachment_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", }, { "range": [21, 23], "attachment_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", }, ], }, ], },)client.Chats.Messages.Send(context.TODO(), {chatId}, linq.ChatMessageSendParams{ Message: linq.F(map[string]any{ Parts: linq.F([]any{ map[string]any{ Type: linq.F("text"), Value: linq.F("Happy birthday 🎂! 🎉🎉"), InlineStickers: linq.F([]any{ map[string]any{ Range: linq.F([]int{15, 17}), AttachmentId: linq.F("550e8400-e29b-41d4-a716-446655440000"), }, map[string]any{ Range: linq.F([]int{19, 21}), AttachmentId: linq.F("7c9e6679-7425-40de-944b-e07fc1f90ae7"), }, map[string]any{ Range: linq.F([]int{21, 23}), AttachmentId: linq.F("7c9e6679-7425-40de-944b-e07fc1f90ae7"), }, }), }, }), }),})The same array works on POST /v3/chats and POST /v3/messages, on any text part.
| Field | Required | Type | Description |
|---|---|---|---|
range | Yes | array<integer> | Character range `[start, end)` in the `value` string that the sticker replaces. `start` is inclusive, `end` is exclusive. Those characters are hidden on iMessage and sent as written on SMS and RCS. *Characters are measured as UTF-16 code units. Most characters count as 1; some emoji count as 2.* |
url | No | string (uri) | Linq attachment URL of the sticker image — the `download_url` returned by `POST /v3/attachments`. The image must already be stored with us. To use an image from elsewhere, upload it with `POST /v3/attachments` first and pass `attachment_id`. Exactly one of `url` or `attachment_id` is required. |
attachment_id | No | string (uuid) | Reference to a sticker image pre-uploaded via `POST /v3/attachments`. Exactly one of `url` or `attachment_id` is required. |
Choosing the range
Section titled “Choosing the range”The sticker replaces the characters in its range. On SMS and RCS, recipients see those characters instead, and VoiceOver reads them aloud.
Use a placeholder that fits in the sentence, like an emoji or [cake]. For example, "Sip cup 🥤" with the range on 🥤 shows “Sip cup” followed by the sticker.
Ranges are [start, end) in UTF-16 code units, the same as text decorations. Most characters count as 1; some emoji count as 2.
Limits
Section titled “Limits”- 100 stickers and 10 different images per part. Copies of one image count as one.
- Ranges can’t overlap each other or a
text_decorationsrange, and can’t split a character. Can’t be combined withmentionon the same part. - Animated images arrive as a still.
- No error on SMS or RCS. The stickers are dropped and
valueis sent as written.
Reading them back
Section titled “Reading them back”Text parts include an inline_stickers array in the send response, GET /v3/messages/{messageId}, GET /v3/chats/{chatId}/messages, and the message.* webhooks. Each entry has the range plus the image’s id, url, mime_type and file_name. It’s null on a part with no stickers.
inline_stickers is only set on messages you send. An inline sticker you receive arrives as a separate media part.
Important notes
Section titled “Important notes”- A peel can’t be removed.
operation: "remove"withtype: "sticker"is rejected, and there is noreaction.removedcounterpart. - Stickers stack. Unlike a tapback, a second sticker doesn’t replace the first.
- Only the sender can move a sticker. A recipient can’t drag, resize or rotate one you peeled. Placement is yours alone, on send or via
PATCH. - iMessage only. A peel onto a chat on SMS or RCS is rejected with
2029. Check iMessage capability first, or send a tapback, which SMS and RCS do support.
Related
Section titled “Related”- Reactions — tapbacks and custom emoji on the same endpoint
- Sending Messages — text parts, decorations, and the range convention inline stickers share
- Attachments — pre-upload, reuse, size limits
- Polls — poll votes arrive as sticker reactions
- Webhook Events — the
reaction.addedpayload - API Reference: Add a reaction
- API Reference: Move a sticker
- Error 2029 — stickers are iMessage only