Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.messagebird</groupId>
<artifactId>messagebird-api</artifactId>
<version>6.2.5</version>
<version>6.3.0</version>
<packaging>jar</packaging>

<name>${project.groupId}:${project.artifactId}</name>
Expand Down Expand Up @@ -62,19 +62,51 @@
</properties>
</profile>
<profile>
<!-- Unit tests only — no live API credentials required -->
<id>test</id>
<properties>
<skipTests>false</skipTests>
<messageBirdAccessKey />
<messageBirdMSISDN />
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>false</skipTests>
<excludes>
<exclude>**/ContactTest.java</exclude>
<exclude>**/MessageBirdClientTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<!--
Integration tests — requires live MessageBird credentials:
mvn test -Pintegration \
-DmessageBirdAccessKey=YOUR_KEY \
-DmessageBirdMSISDN=YOUR_MSISDN
-->
<id>integration</id>
<properties>
<skipTests>false</skipTests>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</profile>
<profile>
<!-- Disables strict doclint for JDK 8–10; not needed from JDK 11+ -->
<id>disable-doclint</id>
<activation>
<jdk>[8,11,)</jdk>
<jdk>[1.8,11)</jdk>
</activation>
<properties>
<doclint>none</doclint>
<skipTests>true</skipTests>
<messageBirdAccessKey />
<messageBirdMSISDN />
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class MessageBirdServiceImpl implements MessageBirdService {

private final String accessKey;
private final String serviceUrl;
private final String clientVersion = "6.2.5";
private final String clientVersion = "6.3.0";
private final String userAgentString;
private Proxy proxy = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class ConversationMessage {
private Date updatedDatetime;
private Map<String, Object> source;
private ConversationMessageTag tag;
private ConversationMessageMetadata metadata;
/**
* See: {@link ConversationPlatformConstants}
*/
Expand Down Expand Up @@ -115,6 +116,14 @@ public void setTag(ConversationMessageTag tag) {
this.tag = tag;
}

public ConversationMessageMetadata getMetadata() {
return metadata;
}

public void setMetadata(ConversationMessageMetadata metadata) {
this.metadata = metadata;
}

public String getPlatform() {
return platform;
}
Expand Down Expand Up @@ -146,6 +155,7 @@ public String toString() {
", updatedDatetime=" + updatedDatetime +
", source=" + source +
", tag=" + tag +
", metadata=" + metadata +
", platform='" + platform + '\'' +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.messagebird.objects.conversations;

import java.util.Date;

/**
* Inner metadata attached to a conversation message. Present on both incoming
* messages and status webhook payloads. {@code sender.userId} always contains
* the BSUID when Meta provides one. When both identifiers exist, the phone
* number appears in the parent {@code from} field, not in this object.
*/
public class ConversationMessageMetadata {

private ConversationSenderMetadata sender;
private Date receivedAt;

public ConversationSenderMetadata getSender() {
return sender;
}

public void setSender(ConversationSenderMetadata sender) {
this.sender = sender;
}

public Date getReceivedAt() {
return receivedAt;
}

public void setReceivedAt(Date receivedAt) {
this.receivedAt = receivedAt;
}

@Override
public String toString() {
return "ConversationMessageMetadata{" +
"sender=" + sender +
", receivedAt=" + receivedAt +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.messagebird.objects.conversations;

/**
* Metadata about the sender of a WhatsApp message. {@code userId} always
* contains the BSUID (e.g. "US.13491208655302741918") when Meta supplies one.
* When both a phone number and a BSUID are available, the phone number appears
* in the parent message's {@code from} field — not here.
*/
public class ConversationSenderMetadata {

private String userId;
private String username;
private String displayName;

public String getUserId() {
return userId;
}

public void setUserId(String userId) {
this.userId = userId;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getDisplayName() {
return displayName;
}

public void setDisplayName(String displayName) {
this.displayName = displayName;
}

@Override
public String toString() {
return "ConversationSenderMetadata{" +
"userId='" + userId + '\'' +
", username='" + username + '\'' +
", displayName='" + displayName + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.messagebird.objects.conversations;

/**
* The {@code messageMetadata} block delivered inside status webhook payloads
* (e.g. {@code statusSent}, {@code statusDelivered}). Reflects the original
* message that triggered the status update.
*
* <p>This class is not produced by any SDK request — it is a standalone POJO
* intended for consumers who deserialize incoming webhook payloads in their
* own HTTP handlers. Use it via {@code ObjectMapper.readValue(body, ...)}.
*
* <p>Both {@code from} and {@code to} accept either a phone number or a
* WhatsApp Business-Scoped User ID (BSUID, e.g. "US.13491208655302741918").
* The BSUID is also available via {@code metadata.sender.userId}.
*/
public class ConversationStatusMessageMetadata {

private String id;
private String from;
private String to;
private String type;
private ConversationContent content;
private ConversationMessageMetadata metadata;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getFrom() {
return from;
}

public void setFrom(String from) {
this.from = from;
}

public String getTo() {
return to;
}

public void setTo(String to) {
this.to = to;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public ConversationContent getContent() {
return content;
}

public void setContent(ConversationContent content) {
this.content = content;
}

public ConversationMessageMetadata getMetadata() {
return metadata;
}

public void setMetadata(ConversationMessageMetadata metadata) {
this.metadata = metadata;
}

@Override
public String toString() {
return "ConversationStatusMessageMetadata{" +
"id='" + id + '\'' +
", from='" + from + '\'' +
", to='" + to + '\'' +
", type='" + type + '\'' +
", content=" + content +
", metadata=" + metadata +
'}';
}
}
2 changes: 2 additions & 0 deletions api/src/test/java/com/messagebird/ContactTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.mockito.Mockito;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.junit.Assume.assumeNotNull;
import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals;

/**
Expand All @@ -30,6 +31,7 @@ public class ContactTest {
@BeforeClass
public static void setUpClass() throws UnauthorizedException, GeneralException {
String accessKey = System.getProperty("messageBirdAccessKey");
assumeNotNull("Integration test skipped: set -DmessageBirdAccessKey to run", accessKey);

msisdn = generateMsisdn();

Expand Down
39 changes: 39 additions & 0 deletions api/src/test/java/com/messagebird/ConversationMessagesTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.messagebird;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.messagebird.exceptions.GeneralException;
import com.messagebird.exceptions.NotFoundException;
import com.messagebird.exceptions.UnauthorizedException;
Expand All @@ -23,6 +25,8 @@ public class ConversationMessagesTest {
private static final String JSON_CONVERSATION_MESSAGE_TEXT = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"text\",\"direction\": \"received\",\"content\": {\"text\": \"Hello\"},\"createdDatetime\": \"2018-08-29T11:49:16Z\",\"updatedDatetime\": \"2018-08-29T11:49:16Z\"}";
private static final String JSON_CONVERSATION_MESSAGE_VIDEO = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"video\",\"direction\": \"received\",\"content\": {\"video\": { \"url\": \"https://example.com/video.mp4\" } },\"createdDatetime\": \"2018-08-29T11:49:16Z\",\"updatedDatetime\": \"2018-08-29T11:49:16Z\"}";
private static final String JSON_CONVERSATION_SEND_MESSAGE_RESPONSE = "{\"id\":\"mesid\",\"status\":\"accepted\",\"fallback\":{\"id\":\"mesid\"}}";
private static final String JSON_CONVERSATION_MESSAGE_BSUID = "{\"id\": \"mesid\",\"conversationId\": \"convid\",\"channelId\": \"chanid\",\"status\": \"received\",\"type\": \"text\",\"direction\": \"received\",\"content\": {\"text\": \"Hello\"},\"metadata\": {\"sender\": {\"displayName\": \"Alice\",\"username\": \"alice_shop\",\"userId\": \"US.13491208655302741918\"},\"receivedAt\": \"2025-04-15T16:00:00Z\"},\"createdDatetime\": \"2025-04-15T16:00:00Z\",\"updatedDatetime\": \"2025-04-15T16:00:00Z\"}";
private static final String JSON_STATUS_MESSAGE_METADATA = "{\"id\": \"e5f6a7b8-c9d0-1234-ef01-23456789abcd\",\"from\": \"15551234567\",\"to\": \"US.13491208655302741918\",\"type\": \"text\",\"content\": {\"text\": \"Hello! Your order has been shipped.\"},\"metadata\": {\"sender\": {\"userId\": \"US.13491208655302741918\"},\"receivedAt\": \"0001-01-01T00:00:00Z\"}}";

/**
* Epsilon to use when checking two latitudes or longitudes for equality.
Expand Down Expand Up @@ -183,6 +187,41 @@ public void testViewConversationMessageLocation() throws GeneralException, NotFo
assertEquals(4.911627, location.getLongitude(), EPSILON_LOCATION_EQUALITY);
}

@Test
public void testViewConversationMessageWithBsuidMetadata() throws GeneralException, NotFoundException, UnauthorizedException {
MessageBirdService messageBirdService = SpyService
.expects("GET", "messages/mesid")
.withConversationsAPIBaseURL()
.andReturns(new APIResponse(JSON_CONVERSATION_MESSAGE_BSUID));
MessageBirdClient messageBirdClient = new MessageBirdClient(messageBirdService);

ConversationMessage message = messageBirdClient.viewConversationMessage("mesid");

ConversationMessageMetadata metadata = message.getMetadata();
assertNotNull(metadata);
assertNotNull(metadata.getReceivedAt());
ConversationSenderMetadata sender = metadata.getSender();
assertEquals("Alice", sender.getDisplayName());
assertEquals("alice_shop", sender.getUsername());
assertEquals("US.13491208655302741918", sender.getUserId());
}

@Test
public void testStatusMessageMetadataDeserializes() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

ConversationStatusMessageMetadata md = mapper.readValue(
JSON_STATUS_MESSAGE_METADATA, ConversationStatusMessageMetadata.class);

assertEquals("e5f6a7b8-c9d0-1234-ef01-23456789abcd", md.getId());
assertEquals("15551234567", md.getFrom());
assertEquals("US.13491208655302741918", md.getTo());
assertEquals("text", md.getType());
assertEquals("Hello! Your order has been shipped.", md.getContent().getText());
assertEquals("US.13491208655302741918", md.getMetadata().getSender().getUserId());
}

@Test
public void testViewConversationMessageText() throws GeneralException, NotFoundException, UnauthorizedException {
MessageBirdService messageBirdService = SpyService
Expand Down
6 changes: 5 additions & 1 deletion api/src/test/java/com/messagebird/MessageBirdClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import static org.junit.Assume.assumeNotNull;

import java.math.BigInteger;
import java.util.Collections;
Expand Down Expand Up @@ -44,7 +45,10 @@ public class MessageBirdClientTest {
@BeforeClass
public static void setUpClass() {
messageBirdAccessKey = System.getProperty("messageBirdAccessKey");
messageBirdMSISDN = new BigInteger(System.getProperty("messageBirdMSISDN"));
String msisdn = System.getProperty("messageBirdMSISDN");
assumeNotNull("Integration test skipped: set -DmessageBirdAccessKey and -DmessageBirdMSISDN to run",
messageBirdAccessKey, msisdn);
messageBirdMSISDN = new BigInteger(msisdn);
}

@Before
Expand Down