diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c
index 150cf42f2..6180a48e6 100644
--- a/daemon/call_interfaces.c
+++ b/daemon/call_interfaces.c
@@ -609,6 +609,8 @@ static void call_ng_flags_flags(struct sdp_ng_flags *out, str *s, void *dummy) {
 		out->reset = 1;
 	else if (!str_cmp(s, "all"))
 		out->all = 1;
+	else if (!str_cmp(s, "fragment"))
+		out->fragment = 1;
 	else if (!str_cmp(s, "port-latching"))
 		out->port_latching = 1;
 	else if (!str_cmp(s, "generate-mid"))
@@ -833,7 +835,7 @@ static const char *call_offer_answer_ng(bencode_item_t *input,
 	}
 
 	errstr = "Failed to parse SDP";
-	if (sdp_parse(&sdp, &parsed))
+	if (sdp_parse(&sdp, &parsed, &flags))
 		goto out;
 
 	if (flags.loop_protect && sdp_is_duplicate(&parsed)) {
@@ -854,7 +856,8 @@ static const char *call_offer_answer_ng(bencode_item_t *input,
 	* to establish session with another rtpengine2 even though rtpengine1
 	* might have persisted part of the session. rtpengine2 deletes previous
 	* call in memory and recreates an OWN call in redis */
-	if (opmode == OP_OFFER) {
+	// SDP fragments for trickle ICE must always operate on an existing call
+	if (opmode == OP_OFFER && !flags.fragment) {
 		if (call) {
 			if (IS_FOREIGN_CALL(call)) {
 				/* destroy call and create new one */
@@ -909,8 +912,11 @@ static const char *call_offer_answer_ng(bencode_item_t *input,
 		recording_start(call, NULL, &flags.metadata);
 
 	ret = monologue_offer_answer(monologue, &streams, &flags);
-	if (!ret)
-		ret = sdp_replace(chopper, &parsed, monologue->active_dialogue, &flags);
+	if (!ret) {
+		// SDP fragments for trickle ICE are consumed with no replacement returned
+		if (!flags.fragment)
+			ret = sdp_replace(chopper, &parsed, monologue->active_dialogue, &flags);
+	}
 
 	struct recording *recording = call->recording;
 	if (recording != NULL) {
@@ -943,7 +949,8 @@ static const char *call_offer_answer_ng(bencode_item_t *input,
 	if (ret)
 		goto out;
 
-	bencode_dictionary_add_string(output, "sdp", chopper->output->str);
+	if (chopper->output->len)
+		bencode_dictionary_add_string(output, "sdp", chopper->output->str);
 
 	errstr = NULL;
 out:
diff --git a/daemon/sdp.c b/daemon/sdp.c
index 43a0c4a59..4ab9fd100 100644
--- a/daemon/sdp.c
+++ b/daemon/sdp.c
@@ -877,7 +877,7 @@ static int parse_attribute(struct sdp_attribute *a) {
 	return ret;
 }
 
-int sdp_parse(str *body, GQueue *sessions) {
+int sdp_parse(str *body, GQueue *sessions, const struct sdp_ng_flags *flags) {
 	char *b, *end, *value, *line_end, *next_line;
 	struct sdp_session *session = NULL;
 	struct sdp_media *media = NULL;
@@ -915,8 +915,12 @@ int sdp_parse(str *body, GQueue *sessions) {
 		}
 
 		errstr = "SDP doesn't start with a session definition";
-		if (!session && b[0] != 'v')
-			goto error;
+		if (!session && b[0] != 'v') {
+			if (!flags->fragment)
+				goto error;
+			else
+				goto new_session; // allowed for trickle ICE SDP fragments
+		}
 
 		str value_str;
 		str_init_len(&value_str, value, line_end - value);
@@ -929,6 +933,7 @@ int sdp_parse(str *body, GQueue *sessions) {
 				if (value[0] != '0')
 					goto error;
 
+new_session:
 				session = g_slice_alloc0(sizeof(*session));
 				g_queue_init(&session->media_streams);
 				attrs_init(&session->attributes);
diff --git a/include/call_interfaces.h b/include/call_interfaces.h
index b39a6e012..f106bb5a3 100644
--- a/include/call_interfaces.h
+++ b/include/call_interfaces.h
@@ -59,6 +59,7 @@ struct sdp_ng_flags {
 	    dtls_passive:1,
 	    reset:1,
 	    all:1,
+	    fragment:1,
 	    record_call:1,
 	    loop_protect:1,
 	    always_transcode:1,
diff --git a/include/sdp.h b/include/sdp.h
index 3f208b75e..ee548ee25 100644
--- a/include/sdp.h
+++ b/include/sdp.h
@@ -19,7 +19,7 @@ struct sdp_chopper {
 
 void sdp_init(void);
 
-int sdp_parse(str *body, GQueue *sessions);
+int sdp_parse(str *body, GQueue *sessions, const struct sdp_ng_flags *);
 int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *);
 void sdp_free(GQueue *sessions);
 int sdp_replace(struct sdp_chopper *, GQueue *, struct call_monologue *, struct sdp_ng_flags *);
diff --git a/utils/rtpengine-ng-client b/utils/rtpengine-ng-client
index f5dfb7f48..f1a517759 100755
--- a/utils/rtpengine-ng-client
+++ b/utils/rtpengine-ng-client
@@ -62,6 +62,7 @@ GetOptions(
 	'address=s'			=> \$options{'address'},
 	'pad-crypto'			=> \$options{'pad crypto'},
 	'generate-mid'			=> \$options{'generate mid'},
+	'fragment'			=> \$options{'fragment'},
 ) or die;
 
 my $cmd = shift(@ARGV) or die;
@@ -74,7 +75,7 @@ for my $x (split(/,/, 'from-tag,to-tag,call-id,transport protocol,media address,
 for my $x (split(/,/, 'TOS,delete-delay')) {
 	defined($options{$x}) and $packet{$x} = $options{$x};
 }
-for my $x (split(/,/, 'trust address,symmetric,asymmetric,force,strict source,media handover,sip source address,reset,port latching,no rtcp attribute,loop protect,record call,always transcode,all,pad crypto,generate mid')) {
+for my $x (split(/,/, 'trust address,symmetric,asymmetric,force,strict source,media handover,sip source address,reset,port latching,no rtcp attribute,loop protect,record call,always transcode,all,pad crypto,generate mid,fragment')) {
 	defined($options{$x}) and push(@{$packet{flags}}, $x);
 }
 for my $x (split(/,/, 'origin,session connection')) {