You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SendGrid签名验证始终失败,请求排查解决方案

问题:SendGrid Webhook签名验证始终失败

我正在实现SendGrid签名验证功能,已在SendGrid Webhook中配置回调函数,目前能正常获取Webhook返回的signature(签名)和timestamp(时间戳),但签名验证一直不通过。

需求说明:当项目通过SendGrid API密钥触发邮件发送时,触发Webhook事件,获取签名和时间戳完成验证,验证通过后将载荷存入数据库。

以下是我的代码:

Controller代码

@PostMapping("/events")  
public void webhookEvents(@RequestBody Set<EmailEvents> emailEvents, @RequestHeader("X-Twilio-Email-Event-Webhook-Timestamp") String timestamp,
@RequestHeader("X-Twilio-Email-Event-Webhook-Signature") String signature) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException, SignatureException, InvalidKeyException {

log.info("Inside webhookEvents...");
log.info("Signature ----->"+signature);
log.info("Timestamp ----->"+timestamp);

boolean verify = emailDataService.verifySendgridSignature(emailEvents,signature,timestamp);
log.info("Signature Verification : "+verify);
emailDataService.webhookEvents(emailEvents);
}

EmailEvents实体类

public class EmailEvents {

public String email;

public int timestamp;
@SerializedName("smtp-id")
public String smtp_id;

public String event;
public ArrayList<String> category;
public String sg_event_id;
public String sg_message_id;

private String useragent;

private String ip;

private boolean sg_machine_open;

private String emailUUID;

private String sentDate;

}

EmailDataService业务类

private EventWebhook ew =  new EventWebhook();
final String publicKey = "XXXXXX";

public boolean verifySendgridSignature(Set<EmailEvents> emailEvents,String signature,String timestamp) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, IOException, SignatureException, InvalidKeyException {

    String payload = new ObjectMapper().writeValueAsString(emailEvents);
    final ECPublicKey ellipticCurvePublicKey = ew.ConvertPublicKeyToECDSA(publicKey);
    final boolean valid = ew.VerifySignature(ellipticCurvePublicKey,payload , signature, timestamp);
    System.out.println("Valid Signature: " + valid);
    return valid;
}

EventWebhook工具类

public class EventWebhook {

public java.security.interfaces.ECPublicKey ConvertPublicKeyToECDSA(String publicKey)
        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
    Security.addProvider(new BouncyCastleProvider());
    byte[] publicKeyInBytes = Base64.getDecoder().decode(publicKey);
    KeyFactory factory = KeyFactory.getInstance("ECDSA", "BC");
    return (ECPublicKey) factory.generatePublic(new X509EncodedKeySpec(publicKeyInBytes));
}

public boolean VerifySignature(ECPublicKey publicKey, String payload, String signature, String timestamp)
        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, IOException {
    return VerifySignature(publicKey, payload.getBytes(), signature, timestamp);
}

public boolean VerifySignature(ECPublicKey publicKey, byte[] payload, String signature, String timestamp)
        throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, IOException {

    // prepend the payload with the timestamp
    final ByteArrayOutputStream payloadWithTimestamp = new ByteArrayOutputStream();
    payloadWithTimestamp.write(timestamp.getBytes());
    payloadWithTimestamp.write(payload);

    // create the signature object
    final Signature signatureObject = Signature.getInstance("SHA256withECDSA", "BC");
    signatureObject.initVerify(publicKey);
    signatureObject.update(payloadWithTimestamp.toByteArray());

    // decode the signature
    final byte[] signatureInBytes = Base64.getDecoder().decode(signature);

    // verify the signature
    return signatureObject.verify(signatureInBytes);
  }
 }

排查方向

  • 公钥格式问题:确认从SendGrid获取的公钥是否完整,若包含-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----标识,需先去除再进行Base64解码。
  • Payload序列化不一致:SendGrid签名基于原始请求体字节流生成,而代码中用ObjectMapper将解析后的对象重新序列化,可能因字段顺序、空格、转义符差异导致哈希值不匹配。建议直接读取原始请求体的字节数组,而非通过@RequestBody解析后再序列化。
  • 字符编码不统一:拼接timestamp和payload时,显式指定UTF-8编码(SendGrid默认编码),避免系统默认编码差异。例如将timestamp.getBytes()改为timestamp.getBytes(StandardCharsets.UTF_8)。
  • 签名算法匹配:尝试将签名算法改为SHA256withECDSAinP1363Format,部分环境下该格式与SendGrid签名生成逻辑更匹配。
  • 时间戳有效性:添加时间戳时效性验证,确保请求未被重放,同时确认timestamp与签名属于同一请求。

内容的提问来源于stack exchange,提问作者Dhanyesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 13:27:03