Inbound Events
The core's record of which inbound events it has already seen, for a connector that receives events over a socket rather than a webhook — accept each event once before queueing it, and let a replay fall away.
Subscriby\Connector\Core\InboundEvents. Every event a platform sends a connector passes the core's dedupe gate before a handler runs, so a webhook retried after a timeout, or a batch delivered twice, reaches the handler once. For a webhook connector the gate is the inbound middleware: the core records every envelope your InboundGateway decodes before the request reaches your route, and you call nothing. For a gateway connector there is no request: your worker holds a socket open, builds the envelopes itself and must record each one here before queueing it. The Discord connector's worker is the first caller.
accept()
use Subscriby\Connector\Core\InboundEvents;
use Subscriby\Connector\Data\InboundEnvelope;
final class ExampleSocketSink
{
public function __construct(
private readonly InboundEvents $inbound,
) {}
public function received(string $sessionId, int $sequence, string $name, array $data): void
{
$envelope = new InboundEnvelope(
connector: 'example',
installation: $this->installationFor($data),
idempotencyKey: $sessionId.':'.$sequence,
kind: strtolower($name),
payload: $data,
receivedAt: new DateTimeImmutable,
);
if (! $this->inbound->accept($envelope)) {
return; // a replay after a resumed session: already handled, or about to be
}
HandleExampleEvent::dispatch($envelope)->onQueue('connectors');
}
}| Method | Returns | Meaning |
|---|---|---|
accept(InboundEnvelope $envelope) | bool | True the first time the envelope's idempotencyKey is seen for its connector; false for every replay. |
The key is scoped to the connector, so two connectors may use the same key words without colliding. The first copy is recorded by an insert that races a unique index, which is what makes two copies arriving on two workers safe: only one of them is told true.
Choosing the key
The key must be stable across replays and unique across events. A platform that numbers events per session gives you the pair for free (the session id and the sequence, as Discord's gateway does); a platform that does not should have an event id in the payload. Never derive the key from the time of receipt, which differs on every replay.
Keep the socket's loop quick
accept() is one indexed insert, which is as much as a socket's loop should wait for. Everything the event causes belongs on a queue. The core forgets a recorded key after a week, long after any platform could still be replaying the event it names, so the table never grows with the traffic.
How is this guide?
Last updated on
Portal Design
A project's portal design as a connector's creator surface edits it — the draft beside the published design, every choice with its label and options, a signed preview address, and the saves, publish, discard and reset the dashboard's designer makes.
Conformance Kit
The suite every connector passes before review — what "passes the kit" means, how it runs, how a rule fails, the report it returns, and the twenty-three rules grouped by what they check.