diff --git a/dapps/faucet/packages/faucet.go b/dapps/faucet/packages/faucet.go
index dd40e51fffa73802939bc5881ffed6fd8089dc6b..d4e5788eb386969a6251c05a3272b32b7a275586 100644
--- a/dapps/faucet/packages/faucet.go
+++ b/dapps/faucet/packages/faucet.go
@@ -1,7 +1,6 @@
 package faucet
 
 import (
-	"errors"
 	"fmt"
 	"sync"
 	"time"
@@ -16,13 +15,6 @@ import (
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
 	"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
 	"github.com/iotaledger/goshimmer/plugins/issuer"
-	"github.com/iotaledger/hive.go/events"
-)
-
-var (
-	// ErrFundingTxNotBookedInTime is returned when a funding transaction didn't get booked
-	// by this node in the maximum defined await time for it to get booked.
-	ErrFundingTxNotBookedInTime = errors.New("funding transaction didn't get booked in time")
 )
 
 // New creates a new faucet using the given seed and tokensPerRequest config.
@@ -80,7 +72,10 @@ func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID strin
 	}
 
 	// prepare value payload with value factory
-	payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
+	payload, err := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
+	if err != nil {
+		return nil, "", fmt.Errorf("failed to issue transaction: %w", err)
+	}
 
 	// attach to message layer
 	msg, err = issuer.IssuePayload(payload)
@@ -91,42 +86,13 @@ func (f *Faucet) SendFunds(msg *message.Message) (m *message.Message, txID strin
 	// block for a certain amount of time until we know that the transaction
 	// actually got booked by this node itself
 	// TODO: replace with an actual more reactive way
-	bookedInTime := f.awaitTransactionBooked(tx.ID(), f.maxTxBookedAwaitTime)
-	if !bookedInTime {
-		return nil, "", fmt.Errorf("%w: tx %s", ErrFundingTxNotBookedInTime, tx.ID().String())
+	if err := valuetransfers.AwaitTransactionToBeBooked(tx.ID(), f.maxTxBookedAwaitTime); err != nil {
+		return nil, "", fmt.Errorf("%w: tx %s", err, tx.ID().String())
 	}
 
 	return msg, tx.ID().String(), nil
 }
 
-// awaitTransactionBooked awaits maxAwait for the given transaction to get booked.
-func (f *Faucet) awaitTransactionBooked(txID transaction.ID, maxAwait time.Duration) bool {
-	booked := make(chan struct{}, 1)
-	// exit is used to let the caller exit if for whatever
-	// reason the same transaction gets booked multiple times
-	exit := make(chan struct{})
-	defer close(exit)
-	closure := events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata, decisionPending bool) {
-		defer cachedTransaction.Release()
-		defer cachedTransactionMetadata.Release()
-		if cachedTransaction.Unwrap().ID() != txID {
-			return
-		}
-		select {
-		case booked <- struct{}{}:
-		case <-exit:
-		}
-	})
-	valuetransfers.Tangle().Events.TransactionBooked.Attach(closure)
-	defer valuetransfers.Tangle().Events.TransactionBooked.Detach(closure)
-	select {
-	case <-time.After(maxAwait):
-		return false
-	case <-booked:
-		return true
-	}
-}
-
 // collectUTXOsForFunding iterates over the faucet's UTXOs until the token threshold is reached.
 // this function also returns the remainder balance for the given outputs.
 func (f *Faucet) collectUTXOsForFunding() (outputIds []transaction.OutputID, addrsIndices map[uint64]struct{}, remainder int64) {
diff --git a/dapps/valuetransfers/dapp.go b/dapps/valuetransfers/dapp.go
index e16014d0ac8a4aebb036418385108c67ea88275d..60adff59bb18cc52bec8690144c53a0dd2654bb8 100644
--- a/dapps/valuetransfers/dapp.go
+++ b/dapps/valuetransfers/dapp.go
@@ -1,6 +1,7 @@
 package valuetransfers
 
 import (
+	"errors"
 	"os"
 	"sync"
 	"time"
@@ -10,6 +11,7 @@ import (
 	valuepayload "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/payload"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tipmanager"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
 	"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
 	messageTangle "github.com/iotaledger/goshimmer/packages/binary/messagelayer/tangle"
 	"github.com/iotaledger/goshimmer/packages/shutdown"
@@ -44,6 +46,10 @@ func init() {
 }
 
 var (
+	// ErrTransactionWasNotBookedInTime is returned if a transaction did not get booked
+	// within the defined await time.
+	ErrTransactionWasNotBookedInTime = errors.New("transaction could not be booked in time")
+
 	// app is the "plugin" instance of the value-transfers application.
 	app     *node.Plugin
 	appOnce sync.Once
@@ -217,7 +223,35 @@ func TipManager() *tipmanager.TipManager {
 // ValueObjectFactory returns the ValueObjectFactory singleton.
 func ValueObjectFactory() *tangle.ValueObjectFactory {
 	valueObjectFactoryOnce.Do(func() {
-		valueObjectFactory = tangle.NewValueObjectFactory(TipManager())
+		valueObjectFactory = tangle.NewValueObjectFactory(Tangle(), TipManager())
 	})
 	return valueObjectFactory
 }
+
+// AwaitTransactionToBeBooked awaits maxAwait for the given transaction to get booked.
+func AwaitTransactionToBeBooked(txID transaction.ID, maxAwait time.Duration) error {
+	booked := make(chan struct{}, 1)
+	// exit is used to let the caller exit if for whatever
+	// reason the same transaction gets booked multiple times
+	exit := make(chan struct{})
+	defer close(exit)
+	closure := events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata, decisionPending bool) {
+		defer cachedTransaction.Release()
+		defer cachedTransactionMetadata.Release()
+		if cachedTransaction.Unwrap().ID() != txID {
+			return
+		}
+		select {
+		case booked <- struct{}{}:
+		case <-exit:
+		}
+	})
+	Tangle().Events.TransactionBooked.Attach(closure)
+	defer Tangle().Events.TransactionBooked.Detach(closure)
+	select {
+	case <-time.After(maxAwait):
+		return ErrTransactionWasNotBookedInTime
+	case <-booked:
+		return nil
+	}
+}
diff --git a/dapps/valuetransfers/packages/tangle/errors.go b/dapps/valuetransfers/packages/tangle/errors.go
index 3ada3b8ae0f57de974ed9bcc4a11012a6db3b5c4..935661ea0a2d659f9d37c6edad5ed3260af28983 100644
--- a/dapps/valuetransfers/packages/tangle/errors.go
+++ b/dapps/valuetransfers/packages/tangle/errors.go
@@ -11,4 +11,7 @@ var (
 
 	// ErrPayloadInvalid represents an error type that is triggered when an invalid payload is detected.
 	ErrPayloadInvalid = errors.New("payload invalid")
+
+	// ErrDoubleSpendForbidden represents an error that is triggered when a user tries to issue a double spend.
+	ErrDoubleSpendForbidden = errors.New("it is not allowed to issue a double spend")
 )
diff --git a/dapps/valuetransfers/packages/tangle/factory.go b/dapps/valuetransfers/packages/tangle/factory.go
index 6720e5d2f9c4361a3a4947706db17e848f16ffd1..106ee55a5b4ff1108e84e18d896dac6f2321e96b 100644
--- a/dapps/valuetransfers/packages/tangle/factory.go
+++ b/dapps/valuetransfers/packages/tangle/factory.go
@@ -9,13 +9,15 @@ import (
 
 // ValueObjectFactory acts as a factory to create new value objects.
 type ValueObjectFactory struct {
+	tangle     *Tangle
 	tipManager *tipmanager.TipManager
 	Events     *ValueObjectFactoryEvents
 }
 
 // NewValueObjectFactory creates a new ValueObjectFactory.
-func NewValueObjectFactory(tipManager *tipmanager.TipManager) *ValueObjectFactory {
+func NewValueObjectFactory(tangle *Tangle, tipManager *tipmanager.TipManager) *ValueObjectFactory {
 	return &ValueObjectFactory{
+		tangle:     tangle,
 		tipManager: tipManager,
 		Events: &ValueObjectFactoryEvents{
 			ValueObjectConstructed: events.NewEvent(valueObjectConstructedEvent),
@@ -25,13 +27,27 @@ func NewValueObjectFactory(tipManager *tipmanager.TipManager) *ValueObjectFactor
 
 // IssueTransaction creates a new value object including tip selection and returns it.
 // It also triggers the ValueObjectConstructed event once it's done.
-func (v *ValueObjectFactory) IssueTransaction(tx *transaction.Transaction) *payload.Payload {
+func (v *ValueObjectFactory) IssueTransaction(tx *transaction.Transaction) (valueObject *payload.Payload, err error) {
 	parent1, parent2 := v.tipManager.Tips()
 
-	valueObject := payload.New(parent1, parent2, tx)
+	// check if the tx that is supposed to be issued is a double spend
+	tx.Inputs().ForEach(func(outputId transaction.OutputID) bool {
+		v.tangle.TransactionOutput(outputId).Consume(func(output *Output) {
+			if output.ConsumerCount() >= 1 {
+				err = ErrDoubleSpendForbidden
+			}
+		})
+
+		return err == nil
+	})
+	if err != nil {
+		return
+	}
+
+	valueObject = payload.New(parent1, parent2, tx)
 	v.Events.ValueObjectConstructed.Trigger(valueObject)
 
-	return valueObject
+	return
 }
 
 // ValueObjectFactoryEvents represent events happening on a ValueObjectFactory.
diff --git a/dapps/valuetransfers/packages/transaction/inputs.go b/dapps/valuetransfers/packages/transaction/inputs.go
index bfc8ea13980fa6ead451063438495904ab2e51b9..f5071c62cebbed30e2410292a82166732d098a73 100644
--- a/dapps/valuetransfers/packages/transaction/inputs.go
+++ b/dapps/valuetransfers/packages/transaction/inputs.go
@@ -104,7 +104,7 @@ func (inputs *Inputs) Bytes() (bytes []byte) {
 
 // ForEach iterates through the referenced Outputs and calls the consumer function for every Output. The iteration can
 // be aborted by returning false in the consumer.
-func (inputs *Inputs) ForEach(consumer func(outputId OutputID) bool) bool {
+func (inputs *Inputs) ForEach(consumer func(outputID OutputID) bool) bool {
 	return inputs.OrderedMap.ForEach(func(key, value interface{}) bool {
 		return value.(*orderedmap.OrderedMap).ForEach(func(key, value interface{}) bool {
 			return consumer(value.(OutputID))
diff --git a/plugins/webapi/value/sendtransaction/handler.go b/plugins/webapi/value/sendtransaction/handler.go
index dda59ab8c3d80fbe5c3b69a08dd82254acc81b6c..56bc66b2a8c549ae5800a501485e640d7153f640 100644
--- a/plugins/webapi/value/sendtransaction/handler.go
+++ b/plugins/webapi/value/sendtransaction/handler.go
@@ -2,6 +2,8 @@ package sendtransaction
 
 import (
 	"net/http"
+	"sync"
+	"time"
 
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers"
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
@@ -9,8 +11,16 @@ import (
 	"github.com/labstack/echo"
 )
 
+var (
+	sendTxMu           sync.Mutex
+	maxBookedAwaitTime = 5 * time.Second
+)
+
 // Handler sends a transaction.
 func Handler(c echo.Context) error {
+	sendTxMu.Lock()
+	defer sendTxMu.Unlock()
+
 	var request Request
 	if err := c.Bind(&request); err != nil {
 		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
@@ -28,12 +38,18 @@ func Handler(c echo.Context) error {
 	}
 
 	// Prepare value payload and send the message to tangle
-	payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
+	payload, err := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
+	if err != nil {
+		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
+	}
 	_, err = issuer.IssuePayload(payload)
 	if err != nil {
 		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
 	}
 
+	if err := valuetransfers.AwaitTransactionToBeBooked(tx.ID(), maxBookedAwaitTime); err != nil {
+		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
+	}
 	return c.JSON(http.StatusOK, Response{TransactionID: tx.ID().String()})
 }
 
diff --git a/plugins/webapi/value/testsendtxn/handler.go b/plugins/webapi/value/testsendtxn/handler.go
index 222e919ede5d6b2390bd0540475b5f0cde0be4f4..15b7a7673bb58b16fe452f9d22412e827006f531 100644
--- a/plugins/webapi/value/testsendtxn/handler.go
+++ b/plugins/webapi/value/testsendtxn/handler.go
@@ -66,8 +66,11 @@ func Handler(c echo.Context) error {
 	tx := transaction.New(inputs, outputs)
 
 	// Prepare value payload and send the message to tangle
-	payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
-	_, err := issuer.IssuePayload(payload)
+	payload, err := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
+	if err != nil {
+		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
+	}
+	_, err = issuer.IssuePayload(payload)
 	if err != nil {
 		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
 	}