Email Magic Link

EchoGuard Email Magic Link

EchoGuard provides a simple and secure email magic link solution that allows you to authenticate users by proving that they own their email address. An email containing a link with a secret query param is sent to an address they provide. Once they click the link, they are directed to a page that extracts the query param to verify they clicked a link only they could have received.

Set up your Callback URL

First, create an endpoint on your backend that accepts a POST request. We will send a webhook to this endpoint when the user clicks the magic link. The request body will contain the user's email address and any metadata you provided when you made the API call, as follows:

  "confirmationId": [CONFIRMATION-ID],
  "email": [USER'S-PHONE-NUMBER],
  "validationStatus": "valid",
  "metadata": {
    [KEY-1]: [VALUE-1]
  "timestamp": [TIME-OF-VERIFICATION]

It will additionally contain an x-signature header that is a SHA-256 ECDSA signature of the request body. You can, optionally, use this signature to verify that the request came from us. To do so, you will need to use our public key, located here: LINK (opens in a new tab). Example verification code as follows:

Express Middleware
const crypto = require('crypto');
const axios = require('axios');
const fetchPublicKey = async () => { const response = await
axios.get(''); return; };
function signatureVerificationMiddleware(req, res, next) { const
verifyDataWithECDSA = async (data, signature, publicKey) => { const verify =
crypto.createVerify('SHA256'); verify.update(data); return verify.verify( { key:
publicKey, }, signature, 'base64' ); };
(async () => { try { const receivedData = JSON.stringify(req.body); const
receivedSignature = req.headers['x-signature']; const publicKey = await
fetchPublicKey(); const isValidSignature = await
verifyDataWithECDSA(receivedData, receivedSignature, publicKey);
    if (isValidSignature) {
      next(); // Proceed to the next middleware or route handler
    } else {
      res.status(403).send('Invalid Signature');
    } catch (error) {
      console.error("Error verifying signature:", error);
      res.status(500).send('Internal Server Error');
})(); }

Set Up Your Landing URL

Now create a static page on your frontend that will be the landing page that the user arrives on after clicking the magic link. If your application is a mobile app, this would be a deep link path. This page/path should contain a script that makes a request to the EchoGuard API to verify the user's email address. The request should include the confirmation secret string, that you derive from the query params of the URL as follows:

Landing URL
const confirmationSecret = new URLSearchParams(
const response = await fetch(
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    body: JSON.stringify({
  const data = await response.json();
  // handle the response

API Call to Initiate Magic Link Flow

Now that you've set up your callback URL, you can make a request to our API to confirm a user's email address. You can also, optionally, provide metadata that will be included in the webhook so that you can more easily track the status of the email confirmation "session" once we send the webhook based on your backend architecture.

curl --request POST \
     --url \
     --header 'Content-Type: application/json' \
     --header 'x-api-key: [YOUR-API-KEY]' \
     --data-raw '
    "email": [USERS-EMAIL-ADDRESS],
    "callbackUrl": [YOUR-CALLBACK-URL],
    "landingUrl": [YOUR-LANDING-URL],
    "metadata": {
        [KEY-1]: [VALUE-1],

In response, you will receive the following JSON object:

  "confirmationId": [CONFIRMATION-ID],
  "metadata": {
        [KEY-1]: [VALUE-1],
  "expiresAt": "2023-08-30T19:12:19.293Z"

At this point, the email containing the magic link has been sent to the user. The link will take them to your landingUrl, with an added confirmation query param. The landingUrl should extract this query param and send it to " (opens in a new tab)".

Notifying the Client of Verification Status

We will only send the webhook if the user's verification was successful. Each email confirmation session is valid for 5 minutes as indicated by the expiresAt field we send when you initiate the flow, so be sure to implement a timeout/refresh strategy. If the user does not click the magic link within that period, the session will expire, and we will not send the webhook.

Since we notify you of verification via webhook, the flow is asynchronous and out-of-band from the client application's perspective, which means that you will need to notify the client of the verification status on your server's initiative. You can do this in at least three ways:

  1. Having the client poll an endpoint on your backend that checks the status of the verification session
  2. Using a WebSocket connection to push the status to the client
  3. Using a server-side event subscription to push the status to the client
  4. In native mobile apps, using a push notification to push the status to the client